Source Code Reference for the CLI.
cli ¶
Modules:
| Name | Description |
|---|---|
cli | Photovoltaic electricity generation potential for different technologies & configurations |
data_model | |
irradiance | |
manual | |
meteo | |
performance | |
plot | |
position | |
power | |
print | |
rich_help_panel_names | Structural components of the command line interface |
series | |
surface | |
time | Important sun and solar surface geometry parameters in calculating the amount of solar radiation that reaches a particular location on the Earth's surface |
write | |
cli ¶
Photovoltaic electricity generation potential for different technologies & configurations
Functions:
| Name | Description |
|---|---|
main | The main entry point for PVIS prototype |
main ¶
main(
version: Annotated[bool, typer_option_version] = None,
verbose: Annotated[int, typer_option_verbose] = 0,
log: Annotated[int | None, typer_option_log] = None,
log_rich_handler: Annotated[
bool, typer_option_log_rich_handler
] = False,
log_file: Annotated[
Path | None, typer_option_logfile
] = None,
) -> None
The main entry point for PVIS prototype
Source code in pvgisprototype/cli/cli.py
@app.callback(no_args_is_help=True)
def main(
version: Annotated[bool, typer_option_version] = None,
verbose: Annotated[int, typer_option_verbose] = 0,
log: Annotated[int | None, typer_option_log] = None,
log_rich_handler: Annotated[bool, typer_option_log_rich_handler] = False,
log_file: Annotated[Path | None, typer_option_logfile] = None,
) -> None:
"""
The main entry point for PVIS prototype
"""
if verbose:
print("Will write verbose output")
state["verbose"] = True
data_model ¶
Modules:
| Name | Description |
|---|---|
analyse | |
data_model | |
inspect | |
visualise | |
analyse ¶
Functions:
| Name | Description |
|---|---|
analyse_centrality | Identify critical nodes (models) that act as hubs or bridges |
analyse_dependency_structure | A long critical path suggests a deep dependency chain, which may impact |
analyse_graph | Calculate fundamental metrics to understand the scale and density of |
analyse_path_length | |
detect_cycles_and_strongly_connected_components |
|
detect_densely_connected_components | Detect clusters of densely connected models to assess modularity |
analyse_centrality ¶
analyse_centrality(source_path: Path) -> None
Identify critical nodes (models) that act as hubs or bridges - High in-degree nodes are critical for many models. - High betweenness nodes act as bridges between modules.
Source code in pvgisprototype/cli/data_model/analyse.py
def analyse_centrality(
source_path: Path,
) -> None:
"""
Identify critical nodes (models) that act as hubs or bridges
- High in-degree nodes are critical for many models.
- High betweenness nodes act as bridges between modules.
"""
graph = build_dependency_graph(source_path=source_path)
in_degrees = dict(graph.in_degree())
out_degrees = dict(graph.out_degree())
betweenness = nx.betweenness_centrality(graph)
pagerank = nx.pagerank(graph)
print("Top 5 nodes by in-degree (most dependencies) :")
for node in sorted(in_degrees, key=in_degrees.get, reverse=True)[:5]:
print(f"{node}: {in_degrees[node]}")
print("\nTop 5 nodes by out-degree (most ) :")
for node in sorted(out_degrees, key=out_degrees.get, reverse=True)[:5]:
print(f"{node}: {out_degrees[node]}")
print("\nTop 5 nodes by betweenness centrality (bridges) :")
for node in sorted(betweenness, key=betweenness.get, reverse=True)[:5]:
print(f"{node}: {betweenness[node]:.4f}")
print("\nTop 5 nodes by PageRank (influence) :")
for node in sorted(pagerank, key=pagerank.get, reverse=True)[:5]:
print(f"{node}: {pagerank[node]:.4f}")
analyse_dependency_structure ¶
analyse_dependency_structure(source_path: Path) -> None
A long critical path suggests a deep dependency chain, which may impact performance or testing complexity.
Source code in pvgisprototype/cli/data_model/analyse.py
def analyse_dependency_structure(
source_path: Path,
) -> None:
"""
A long critical path suggests a deep dependency chain, which may impact
performance or testing complexity.
"""
graph = build_dependency_graph(source_path=source_path)
if nx.is_directed_acyclic_graph(G=graph):
topological_order = list(nx.topological_sort(G=graph))
print("Topological order (first 5):", topological_order[:5])
# Longest path (critical path)
longest_path = nx.dag_longest_path(G=graph)
print(f"Critical path length: {len(longest_path)}")
print(f"Critical path: {longest_path}")
else:
print("Graph is not a DAG. Skipping topological analysis.")
analyse_graph ¶
analyse_graph(source_path: Path) -> None
Calculate fundamental metrics to understand the scale and density of dependencies. - High node/edge counts suggest complexity. - High density (many edges relative to nodes) indicates tightly interconnected models, which may complicate maintenance.
Source code in pvgisprototype/cli/data_model/analyse.py
def analyse_graph(
source_path: Path,
) -> None:
"""
Calculate fundamental metrics to understand the scale and density of
dependencies.
- High node/edge counts suggest complexity.
- High density (many edges relative to nodes) indicates tightly
interconnected models, which may complicate maintenance.
"""
graph = build_dependency_graph(source_path=source_path)
print(f"Number of nodes (models): {graph.number_of_nodes()}")
print(f"Number of edges (dependencies): {graph.number_of_edges()}")
print(f"Graph density: {nx.density(G=graph):.4f}")
print(
f"Is the graph a DAG (Directed Acyclic Graph)? {nx.is_directed_acyclic_graph(G=graph)}"
)
analyse_path_length ¶
analyse_path_length(source_path: Path) -> None
Source code in pvgisprototype/cli/data_model/analyse.py
def analyse_path_length(
source_path: Path,
) -> None:
"""
Interpretation:
A low average path length suggests a "small-world" structure, where
models are reachable in few steps.
"""
graph = build_dependency_graph(source_path=source_path)
if nx.is_strongly_connected(G=graph):
avg_path = nx.average_shortest_path_length(G=graph)
print(f"Average shortest path length: {avg_path:.2f}")
else:
print("Graph is not strongly connected. Calculating for largest component...")
largest_cc = max(nx.strongly_connected_components(G=graph), key=len)
G_sub = graph.subgraph(largest_cc)
avg_path = nx.average_shortest_path_length(G=G_sub)
print(f"Average shortest path in largest SCC: {avg_path:.2f}")
detect_cycles_and_strongly_connected_components ¶
detect_cycles_and_strongly_connected_components(
source_path: Path,
) -> None
- Cycles (e.g., A → B → A) indicate potential design flaws.
- Large Strongly Connected Components suggest tightly interdependent modules needing refactoring.
Source code in pvgisprototype/cli/data_model/analyse.py
def detect_cycles_and_strongly_connected_components(
source_path: Path,
) -> None:
"""
- Cycles (e.g., A → B → A) indicate potential design flaws.
- Large Strongly Connected Components suggest tightly interdependent
modules needing refactoring.
"""
graph = build_dependency_graph(source_path=source_path)
try:
cycles = list(nx.simple_cycles(G=graph))
print(f"Found {len(cycles)} cycles. Examples:")
for cycle in cycles[:3]: # Show first 3
print(" → ".join(cycle))
except nx.NetworkXNoCycle:
print("No cycles detected.")
strongly_connected_components = list(nx.strongly_connected_components(G=graph))
print("Strongly Connected Components")
print(f"Number of components : {len(strongly_connected_components)}")
print(
"Largest component :",
(
max(strongly_connected_components, key=len)
if strongly_connected_components
else "N/A"
),
)
detect_densely_connected_components ¶
detect_densely_connected_components(
source_path: Path,
) -> None
Detect clusters of densely connected models to assess modularity
Interpretation: - Communities may align with functional domains (e.g., solar position, energy yield, temperature). - High modularity suggests well-organized, maintainable code.
Source code in pvgisprototype/cli/data_model/analyse.py
def detect_densely_connected_components(
source_path: Path,
) -> None:
"""
Detect clusters of densely connected models to assess modularity
Interpretation:
- Communities may align with functional domains (e.g., solar position,
energy yield, temperature).
- High modularity suggests well-organized, maintainable code.
"""
graph = build_dependency_graph(source_path=source_path)
G_undirected = graph.to_undirected()
communities = nx_comm.greedy_modularity_communities(G=G_undirected)
print(f"Detected {len(communities)} communities.")
for i, community in enumerate(communities):
print(f"Community {i+1} (size {len(community)}): {community}")
data_model ¶
Functions:
| Name | Description |
|---|---|
main | Inspect data model definitions including YAML files, Python dictionaries |
main ¶
main(
ctx: Context,
verbose: Annotated[bool, Option(help=Verbose)] = False,
log_file: Annotated[
str | None,
Option(--log - file, -l, help="Log file"),
] = LOG_FILE,
log_level: str = LOG_LEVEL,
rich_handler: Annotated[
bool,
Option(--rich, --no - rich, help="Rich handler"),
] = RICH_HANDLER,
)
Inspect data model definitions including YAML files, Python dictionaries and native PVGIS data models.
Source code in pvgisprototype/cli/data_model/data_model.py
@app.callback()
def main(
ctx: typer.Context,
verbose: Annotated[bool, typer.Option(help="Verbose")] = False,
log_file: Annotated[
str | None, typer.Option("--log-file", "-l", help="Log file")
] = LOG_FILE,
log_level: str = LOG_LEVEL,
rich_handler: Annotated[
bool, typer.Option("--rich", "--no-rich", help="Rich handler")
] = RICH_HANDLER,
):
"""
Inspect data model definitions including YAML files, Python dictionaries
and native PVGIS data models.
"""
if verbose:
log_level = "DEBUG"
setup_factory_logger(level=log_level, file=log_file, rich_handler=rich_handler)
# Store logging config in context for child commands
if not ctx.obj:
ctx.obj = {}
ctx.obj["logger_configuration"] = {
"log_level": log_level,
"log_file": log_file,
"rich_handler": rich_handler,
}
inspect ¶
Functions:
| Name | Description |
|---|---|
inspect_pvgis_data_model | |
inspect_python_definition | Inspect a specific dictionary key from a Python module. |
inspect_yaml_definition | |
load_python_module | Dynamically load a Python module from a file path. |
inspect_python_definition ¶
inspect_python_definition(
python_module: Path,
definition: str = "Fingerprint",
attribute: str | None = None,
verbose: Annotated[bool, Option(help=Verbose)] = False,
log_file: Annotated[
str | None,
Option(--log - file, -l, help="Log file"),
] = LOG_FILE,
log_level: str = LOG_LEVEL,
rich_handler: Annotated[
bool,
Option(--rich, --no - rich, help="Rich handler"),
] = RICH_HANDLER,
)
Inspect a specific dictionary key from a Python module.
Source code in pvgisprototype/cli/data_model/inspect.py
def inspect_python_definition(
python_module: Path,
definition: str = "Fingerprint", # The key to look for in the module's dictionary
attribute: str | None = None,
verbose: Annotated[bool, typer.Option(help="Verbose")] = False,
log_file: Annotated[
str | None, typer.Option("--log-file", "-l", help="Log file")
] = LOG_FILE,
log_level: str = LOG_LEVEL,
rich_handler: Annotated[
bool, typer.Option("--rich", "--no-rich", help="Rich handler")
] = RICH_HANDLER,
):
"""Inspect a specific dictionary key from a Python module."""
# Initialize logging
setup_factory_logger(
verbose=verbose,
level=log_level,
file=log_file,
rich_handler=rich_handler,
)
if not definition:
print(f"List all top-level data model definition names")
else:
try:
# Load the data model definitions Python dictionary
module = load_python_module(python_module)
definitions = module.PVGIS_DATA_MODEL_DEFINITIONS
# Extract the specific data model
if definition in definitions:
output = definitions[definition]
if attribute and attribute in output:
output = definitions[definition][attribute]
# Print in YAML format for consistent visualization
print(
yaml.dump(
data=output,
sort_keys=False,
default_flow_style=False,
indent=2
)
)
else:
logger.warning(f"Key '{definition_key}' not found in the definitions dictionary.")
raise typer.Exit(code=1)
except AttributeError:
typer.echo("The module does not have a 'PVGIS_DATA_MODEL_DEFINITIONS' variable.")
raise typer.Exit(code=1)
except Exception as e:
typer.echo(f"Error: {str(e)}")
raise typer.Exit(code=1)
inspect_yaml_definition ¶
inspect_yaml_definition(
yaml_file: Path,
verbose: Annotated[bool, Option(help=Verbose)] = False,
log_file: Annotated[
str | None,
Option(--log - file, -l, help="Log file"),
] = LOG_FILE,
log_level: str = LOG_LEVEL,
rich_handler: Annotated[
bool,
Option(--rich, --no - rich, help="Rich handler"),
] = RICH_HANDLER,
)
Source code in pvgisprototype/cli/data_model/inspect.py
def inspect_yaml_definition(
yaml_file: Path,
verbose: Annotated[bool, typer.Option(help="Verbose")] = False,
log_file: Annotated[
str | None, typer.Option("--log-file", "-l", help="Log file")
] = LOG_FILE,
log_level: str = LOG_LEVEL,
rich_handler: Annotated[
bool, typer.Option("--rich", "--no-rich", help="Rich handler")
] = RICH_HANDLER,
):
""" """
# Initialize logging
setup_factory_logger(
verbose=verbose,
level=log_level,
file=log_file,
rich_handler=rich_handler,
)
print(
yaml.dump(
load_yaml_file(yaml_file),
sort_keys=False,
)
)
load_python_module ¶
Dynamically load a Python module from a file path.
Source code in pvgisprototype/cli/data_model/inspect.py
def load_python_module(module_path: Path) -> Any:
"""Dynamically load a Python module from a file path."""
module_name = module_path.stem
spec = util.spec_from_file_location(module_name, module_path)
if spec is None:
raise ImportError(f"Could not load module from {module_path}")
module = util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
visualise ¶
Functions:
| Name | Description |
|---|---|
visualise_circular_tree | Prototype for the command line |
visualise_graph | Prototype for the command line |
visualise_gravis_d3 | Prototype for the command line |
visualise_hierarchical_graph | Prototype for the command line |
visualise_circular_tree ¶
visualise_circular_tree(
source_path: Annotated[
Path,
Option(
help="Source directory with YAML data model descriptions"
),
] = Path("output/complex_example"),
node_size: Annotated[
int, Option(help="Node size")
] = 20,
) -> None
Prototype for the command line
Source code in pvgisprototype/cli/data_model/visualise.py
def visualise_circular_tree(
source_path: Annotated[
Path, typer.Option(help="Source directory with YAML data model descriptions")
] = Path(
"output/complex_example"
), # definitions.yaml"),
node_size: Annotated[int, typer.Option(help="Node size")] = 20,
) -> None:
"""
Prototype for the command line
"""
generate_circular_tree(
source_path=source_path,
node_size=node_size,
)
visualise_graph ¶
visualise_graph(
source_path: Annotated[
Path,
Option(
help="Source directory with YAML data model descriptions"
),
] = Path("output/complex_example"),
node_size: Annotated[
int, Option(help="Node size")
] = 2400,
parent_node_size: Annotated[
int, Option(help="Parent node size")
] = 800,
) -> None
Prototype for the command line
Source code in pvgisprototype/cli/data_model/visualise.py
def visualise_graph(
source_path: Annotated[
Path, typer.Option(help="Source directory with YAML data model descriptions")
] = Path(
"output/complex_example"
), # definitions.yaml"),
# yaml_file: Path = Path("output/complex_example/complex.yaml"),
node_size: Annotated[int, typer.Option(help="Node size")] = 2400,
parent_node_size: Annotated[int, typer.Option(help="Parent node size")] = 800,
) -> None:
"""
Prototype for the command line
"""
generate_graph(
source_path=source_path,
# yaml_file=yaml_file,
node_size=node_size,
parent_node_size=parent_node_size,
)
visualise_gravis_d3 ¶
visualise_gravis_d3(
ctx: Context,
yaml_file: Annotated[
Path,
Option(help="A YAML PVGIS data model description"),
] = Path("definitions.yaml"),
output_file: Path = Path("data_model_graph.html"),
) -> None
Prototype for the command line
Source code in pvgisprototype/cli/data_model/visualise.py
def visualise_gravis_d3(
ctx: typer.Context, # Access parent context
yaml_file: Annotated[
Path, typer.Option(help="A YAML PVGIS data model description")
] = Path(
"definitions.yaml"
), # definitions.yaml"),
# yaml_file: Path = Path("definitions.yaml/data_model_template.yaml"),
output_file: Path = Path("data_model_graph.html"),
# node_size: Annotated[int, typer.Option(help="Node size")] = 2400,
# parent_node_size: Annotated[int, typer.Option(help="Parent node size")] = 800,
) -> None:
"""
Prototype for the command line
"""
# Get logging config from parent context if available
log_config = getattr(ctx.obj, 'log_config', {}) if ctx and ctx.obj else {}
generate_gravis_d3(
yaml_file=yaml_file,
# yaml_file=yaml_file,
output_file=output_file,
# node_size=node_size,
# parent_node_size=parent_node_size,
**log_config
)
irradiance ¶
Modules:
| Name | Description |
|---|---|
introduction | |
kato_bands | |
limits | |
reflectivity | |
introduction ¶
Functions:
| Name | Description |
|---|---|
solar_irradiance_introduction | A short introduction on solar irradiance |
solar_irradiance_introduction ¶
A short introduction on solar irradiance
Source code in pvgisprototype/cli/irradiance/introduction.py
def solar_irradiance_introduction():
"""A short introduction on solar irradiance"""
introduction = """
[underline]Solar irradiance[/underline] is ...
"""
note = """
PVGIS can model solar irradiance components or read selectively
[magenta]global[/magenta] or [magenta]direct[/magenta] irradiance time series from external datasets.
"""
from rich.panel import Panel
note_in_a_panel = Panel(
"[italic]{}[/italic]".format(note),
title="[bold cyan]Note[/bold cyan]",
width=78,
)
from rich.console import Console
console = Console()
# introduction.wrap(console, 30)
console.print(introduction)
console.print(note_in_a_panel)
console.print(A_PRIMER_ON_SOLAR_IRRADIANCE)
kato_bands ¶
limits ¶
Functions:
| Name | Description |
|---|---|
calculate_physical_limits | Calculate physically possible limits. |
calculate_rare_limits | Calculate extremely rare limits. |
print_limits_table | Print table of physically possible irradiance limits |
calculate_physical_limits ¶
calculate_physical_limits(
solar_zenith: float,
air_temperature: float = 300,
rounding_places: int = 5,
)
Calculate physically possible limits.
Source code in pvgisprototype/cli/irradiance/limits.py
@app.command(
"physical",
no_args_is_help=True,
rich_help_panel=rich_help_panel_irradiance_series,
)
def calculate_physical_limits(
solar_zenith: float,
air_temperature: float = 300,
rounding_places: int = 5,
):
"""Calculate physically possible limits."""
limits = calculate_limits(solar_zenith, air_temperature, PHYSICALLY_POSSIBLE_LIMITS)
print_limits_table(limits_dictionary=limits, rounding_places=rounding_places)
return limits
calculate_rare_limits ¶
calculate_rare_limits(
solar_zenith: float,
air_temperature: float = 300,
rounding_places: int = 5,
)
Calculate extremely rare limits.
Source code in pvgisprototype/cli/irradiance/limits.py
@app.command(
"rare",
no_args_is_help=True,
rich_help_panel=rich_help_panel_irradiance_series,
)
def calculate_rare_limits(
solar_zenith: float,
air_temperature: float = 300,
rounding_places: int = 5,
):
"""Calculate extremely rare limits."""
limits = calculate_limits(solar_zenith, air_temperature, EXTREMELY_RARE_LIMITS)
print_limits_table(limits_dictionary=limits, rounding_places=rounding_places)
return limits
print_limits_table ¶
Print table of physically possible irradiance limits
Source code in pvgisprototype/cli/irradiance/limits.py
def print_limits_table(
limits_dictionary,
rounding_places=ROUNDING_PLACES_DEFAULT,
):
"""Print table of physically possible irradiance limits"""
limits_dictionary = round_float_values(limits_dictionary, rounding_places)
table = Table(box=box.SIMPLE_HEAD)
table.add_column("Component")
table.add_column("Min", justify="right")
table.add_column("Max", justify="right")
for component, limits in limits_dictionary.items():
table.add_row(component, str(limits["Min"]), str(limits["Max"]))
Console().print(table)
reflectivity ¶
Functions:
| Name | Description |
|---|---|
get_reflectivity_factor_for_direct_irradiance_series | Notes |
get_reflectivity_factor_for_nondirect_irradiance | |
get_reflectivity_factor_for_direct_irradiance_series ¶
get_reflectivity_factor_for_direct_irradiance_series(
solar_incidence_series: Annotated[
List[float], typer_argument_solar_incidence_series
],
angular_loss_coefficient: float = ANGULAR_LOSS_COEFFICIENT,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
)
Notes
This CLI function uses an implementation of the solar incidence angle modifier as per Martin & Ruiz (2005). Expected is the angle between the sun-solar-surface vector and the vector normal to the reference solar surface. We call this the typical incidence angle as opposed to the complementary incidence angle defined by Jenčo (1992).
Source code in pvgisprototype/cli/irradiance/reflectivity.py
@app.command(
"direct",
no_args_is_help=True,
help=f"⦟ Solar incidence angle modifier for direct inclined irradiance due to reflectivity by Martin & Ruiz, 2005 {NOT_COMPLETE_CLI}",
short_help=f"⦟ Solar incidence angle modifier for direct irradiance due to reflectivity {NOT_COMPLETE_CLI}",
rich_help_panel=rich_help_panel_toolbox,
)
def get_reflectivity_factor_for_direct_irradiance_series(
solar_incidence_series: Annotated[
List[float], typer_argument_solar_incidence_series
],
angular_loss_coefficient: float = ANGULAR_LOSS_COEFFICIENT,
# csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
# dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
# array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
# uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
# resample_large_series: Annotated[bool, 'Resample large time series?'] = False,
# terminal_width_fraction: Annotated[float, typer_option_uniplot_terminal_width] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
# fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
# metadata: Annotated[bool, typer_option_command_metadata] = False,
# panels: Annotated[bool, typer_option_panels_output] = False,
# index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
):
"""
Notes
-----
This CLI function uses an implementation of the solar incidence angle
modifier as per Martin & Ruiz (2005). Expected is the angle between the
sun-solar-surface vector and the vector normal to the reference solar
surface. We call this the _typical_ incidence angle as opposed to the
_complementary_ incidence angle defined by Jenčo (1992).
"""
reflectivity_factor_for_direct_irradiance_series = (
calculate_reflectivity_factor_for_direct_irradiance_series(
solar_incidence_series=solar_incidence_series,
angular_loss_coefficient=angular_loss_coefficient,
verbose=verbose,
log=log,
)
)
if not quiet:
if verbose > 0:
pass
else:
flat_list = (
reflectivity_factor_for_direct_irradiance_series.flatten().astype(str)
)
csv_str = ",".join(flat_list)
print(csv_str)
get_reflectivity_factor_for_nondirect_irradiance ¶
get_reflectivity_factor_for_nondirect_irradiance(
indirect_angular_loss_coefficient: float,
angular_loss_coefficient: float = ANGULAR_LOSS_COEFFICIENT,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
)
Source code in pvgisprototype/cli/irradiance/reflectivity.py
@app.command(
"indirect",
no_args_is_help=True,
help=f"⦟ Solar incidence angle modifier for non-direct inclined irradiance due to reflectivity by Martin & Ruiz, 2005 {NOT_COMPLETE_CLI}",
short_help=f"⦟ Solar incidence angle modifier for non-direct irradiance due to reflectivity {NOT_COMPLETE_CLI}",
rich_help_panel=rich_help_panel_toolbox,
)
def get_reflectivity_factor_for_nondirect_irradiance(
indirect_angular_loss_coefficient: float,
angular_loss_coefficient: float = ANGULAR_LOSS_COEFFICIENT,
# csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
# dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
# array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
# uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
# resample_large_series: Annotated[bool, 'Resample large time series?'] = False,
# terminal_width_fraction: Annotated[float, typer_option_uniplot_terminal_width] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
# fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
# metadata: Annotated[bool, typer_option_command_metadata] = False,
# panels: Annotated[bool, typer_option_panels_output] = False,
# index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
):
""" """
reflectivity_factor_for_nondirect_irradiance = (
calculate_reflectivity_factor_for_nondirect_irradiance(
indirect_angular_loss_coefficient=indirect_angular_loss_coefficient,
angular_loss_coefficient=angular_loss_coefficient,
verbose=verbose,
)
)
if not quiet:
if verbose > 0:
pass
else:
print(reflectivity_factor_for_nondirect_irradiance)
manual ¶
Functions:
| Name | Description |
|---|---|
update_solar_radiation_dictionary | Examples |
update_solar_radiation_dictionary ¶
update_solar_radiation_dictionary(
lemma: Annotated[str, Argument(help=Lemma)],
lemma_name: Annotated[
str, Argument(help=lemma_name(snake_case))
],
symbol: Annotated[
str, Argument(help="Symbol for Lemma")
],
description: Annotated[
str, Argument(help="Description of Lemma")
],
units: Annotated[str, Argument(help="Units for Lemma")],
tags: Annotated[
str,
Argument(help="Tags for Lemma (comma-separated)"),
],
dictionary_yaml_file: Annotated[
Path,
Option(help="Path to the dictionary YAML file"),
] = Path(".data/solar_radiation_dictionary.yml"),
)
Examples:
pvis manual update "lemma-name" lemma_name "Symbol" "Description" "Units" "tag1, tag2, tag3"
Source code in pvgisprototype/cli/manual.py
@app.command("update", no_args_is_help=True, help="Update")
def update_solar_radiation_dictionary(
lemma: Annotated[str, typer.Argument(help="Lemma")],
lemma_name: Annotated[str, typer.Argument(help="lemma_name (snake_case)")],
symbol: Annotated[str, typer.Argument(help="Symbol for Lemma")],
description: Annotated[str, typer.Argument(help="Description of Lemma")],
units: Annotated[str, typer.Argument(help="Units for Lemma")],
tags: Annotated[str, typer.Argument(help="Tags for Lemma (comma-separated)")],
dictionary_yaml_file: Annotated[
Path, typer.Option(help="Path to the dictionary YAML file")
] = Path(".data/solar_radiation_dictionary.yml"),
):
"""
Examples
--------
pvis manual update "lemma-name" lemma_name "Symbol" "Description" "Units" "tag1, tag2, tag3"
"""
tags_list = [tag.strip() for tag in tags.split(",")]
new_entry = {
"lemma": lemma,
"lemma_name": lemma_name,
"symbol": symbol,
"description": description,
"units": units,
"tags": tags_list,
}
with open(dictionary_yaml_file, "r") as file:
data = yaml.safe_load(file)
existing_lemma = next((v for v in data if v["lemma_name"] == lemma_name), None)
if existing_lemma:
existing_lemma.update(new_entry)
typer.echo(f"Updated existing lemma: {lemma_name}")
else:
data.append(new_entry)
typer.echo(f"Added new lemma: {lemma_name}")
with open(dictionary_yaml_file, "w") as file:
yaml.dump(data, file)
meteo ¶
Modules:
| Name | Description |
|---|---|
introduction | |
meteo | |
tmy | |
introduction ¶
Functions:
| Name | Description |
|---|---|
introduction | A short introduction on the Typical Meteorological Year |
introduction ¶
A short introduction on the Typical Meteorological Year
Source code in pvgisprototype/cli/meteo/introduction.py
def introduction():
"""
A short introduction on the Typical Meteorological Year
"""
introduction = """The [underline]Typical Meteorological Year[/underline]
(TMY) is a dataset designed to represent the most _typical_ weather
conditions for each month at a given location, using historical data. This
dataset is particularly useful for simulations in solar energy and building
performance."""
note = """Internally, [bold]timestamps[/bold] are converted to
[magenta]UTC[/magenta] and [bold]angles[/bold] are measured in
[magenta]radians[/magenta] !
"""
from rich.panel import Panel
note_in_a_panel = Panel(
"[italic]{}[/italic]".format(note),
title="[bold cyan]Note[/bold cyan]",
width=78,
)
from rich.console import Console
console = Console()
# introduction.wrap(console, 30)
console.print(introduction)
console.print(note_in_a_panel)
console.print(A_PRIMER_ON_TYPICAL_METEOROLOGICAL_YEAR)
meteo ¶
Functions:
| Name | Description |
|---|---|
main | Typical Meteorological Year |
main ¶
main(
ctx: Context,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
debug: Annotated[
bool, Option(--debug, help="Enable debug mode")
] = False,
)
Typical Meteorological Year
Source code in pvgisprototype/cli/meteo/meteo.py
@app.callback()
def main(
ctx: typer.Context,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
debug: Annotated[bool, typer.Option(
"--debug",
help="Enable debug mode")] = False,
):
"""
Typical Meteorological Year
"""
# if verbose > 2:
# print(f"Executing command: {ctx.invoked_subcommand}")
if verbose > 0:
print("Will output verbosely")
# state["verbose"] = True
app.debug_mode = debug
tmy ¶
Functions:
| Name | Description |
|---|---|
calculate_degree_days | Calculate the cooling/heating degree days |
tmy | Generate the Typical Meteorological Year (TMY) |
tmy_weighting | Print the available weightings for a meteorological variable or full scheme. |
calculate_degree_days ¶
calculate_degree_days(
location: Annotated[
Tuple[float, float],
Argument(..., help="Latitude, longitude [°]"),
],
years: Annotated[
Tuple[float, float],
Argument(
...,
min=2005,
max=2020,
help="First and last year of calculations",
),
],
meteo: Annotated[
Path,
Argument(
...,
exists=True,
file_okay=True,
dir_okay=False,
writable=False,
readable=True,
resolve_path=True,
help="Directory containing the meteorological data",
),
],
elevation: Annotated[
Path,
Argument(
...,
exists=True,
file_okay=True,
dir_okay=False,
writable=False,
readable=True,
resolve_path=True,
help="Directory containing the digital elevation data",
),
],
monthly: bool = Option(
False, is_flag=True, help="Print monthly averages"
),
)
Calculate the cooling/heating degree days
Args:
Returns: None
Raises: AssertionError: If the command exit code or output doesn't match the expected values.
Example:
Notes: - Refactored from the original C program degreedays_hourly as follows:
-
New flag, Old flag, Type, Old Variable, Description
-
monthly , r , Flag , - , Print monthly averages
-
meteo , g , str , dailyPrefix , Directory containing the meteo files
-
elevation , f , str , elevationFilename , Directory prefix of the DEM files
-
location , d , float, float , latitude, longitude , Latitude, longitude [°]
-
years , y , float, float , yearStart, yearEnd , First year, last year of calculations
Source code in pvgisprototype/cli/meteo/tmy.py
def calculate_degree_days(
location: Annotated[
Tuple[float, float], typer.Argument(..., help="Latitude, longitude [°]")
],
years: Annotated[
Tuple[float, float],
typer.Argument(
..., min=2005, max=2020, help="First and last year of calculations"
),
],
meteo: Annotated[
Path,
typer.Argument(
...,
exists=True,
file_okay=True,
dir_okay=False,
writable=False,
readable=True,
resolve_path=True,
help="Directory containing the meteorological data",
),
],
elevation: Annotated[
Path,
typer.Argument(
...,
exists=True,
file_okay=True,
dir_okay=False,
writable=False,
readable=True,
resolve_path=True,
help="Directory containing the digital elevation data",
),
],
monthly: bool = typer.Option(False, is_flag=True, help="Print monthly averages"),
):
"""Calculate the cooling/heating degree days
Args:
Returns:
None
Raises:
AssertionError: If the command exit code or output doesn't match the expected values.
Example:
Notes:
- Refactored from the original C program `degreedays_hourly` as follows:\n
- New flag, Old flag, Type, Old Variable, Description\n
- monthly , r , Flag , - , Print monthly averages \n
- meteo , g , str , dailyPrefix , Directory containing the meteo files\n
- elevation , f , str , elevationFilename , Directory prefix of the DEM files\n
- location , d , float, float , latitude, longitude , Latitude, longitude [°]\n
- years , y , float, float , yearStart, yearEnd , First year, last year of calculations
"""
pass
tmy ¶
tmy(
longitude: Annotated[
float, typer_argument_longitude_in_degrees
] = float(),
latitude: Annotated[
float, typer_argument_latitude_in_degrees
] = float(),
timestamps: Annotated[
DatetimeIndex, typer_argument_naive_timestamps
] = str(now_datetime()),
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = None,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
convert_longitude_360: Annotated[
bool, typer_option_convert_longitude_360
] = False,
variable: Annotated[
str | None, typer_option_data_variable
] = None,
meteorological_variable: Annotated[
List[MeteorologicalVariable],
Option(
help="Standard name of meteorological variable for Finkelstein-Schafer statistics"
),
] = [all],
global_horizontal_irradiance: Annotated[
Path | None,
typer_option_global_horizontal_irradiance,
] = None,
direct_horizontal_irradiance: Annotated[
Path | None,
typer_option_direct_horizontal_irradiance,
] = None,
temperature_series: Annotated[
TemperatureSeries,
typer_option_temperature_series_for_tmy,
] = average_air_temperature,
relative_humidity_series: Annotated[
RelativeHumiditySeries,
typer_option_relative_humidity_series_for_tmy,
] = average_relative_humidity,
wind_speed_series: Annotated[
WindSpeedSeries,
typer_option_wind_speed_series_for_tmy,
] = average_wind_speed,
solar_position_model: SolarPositionModel = SOLAR_POSITION_ALGORITHM_DEFAULT,
eccentricity_phase_offset: float = value,
eccentricity_amplitude: float = value,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = nearest,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
output_filename: Annotated[
Path, typer_option_output_filename
] = "series_in",
variable_name_as_suffix: Annotated[
bool, typer_option_variable_name_as_suffix
] = True,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = RADIANS,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
weighting_scheme: TypicalMeteorologicalMonthWeightingScheme = ISO_15927_4.value,
plot_statistic: Annotated[
list[TMYStatisticModel],
Option(
help="Select which Finkelstein-Schafer statistics to plot"
),
] = None,
limit_x_axis_to_tmy_extent: Annotated[
bool,
"Limit plot of input time series to temporal extent of TMY",
] = True,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = NoneValue,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
)
Generate the Typical Meteorological Year (TMY)
A typical meteorological year (TMY) is a set of meteorological data with data values for every hour in a year for a given geographical location. The data are selected from hourly data in a longer time period (normally 10 years or more).
An overview of the algorithm for calculating the TMY :
-
Input data : Read at least 10 years of hourly time series over a location
-
Daily Statistics : Compute daily maximum, minimum, and mean for selected variables.
-
Cumulative Distribution Function (CDF) : Compute the CDF of each variable for each month :
3.1 one CDF for each variable, month, and year. For example, for the Global Horizontal Irradiance (GHI) : one for January 2011, one for January 2012 and so on, and for each month the same for the ambient Temperature or other variables.
3.2 one long-term CDF for each variable across all years for each month.
- Finkelstein-Schafer Statistic :
4.1 Compute the absolute difference between the long-term CDF and the candidate month's CDF. 4.2 Compute the weighted sum (WS) of these differences for each month and year.
-
Typical Months : Rank months by the lowest WS for each month (e.g., rank all Januaries).
-
Month Selection :
-
ISO method: Select months based on wind speed similarity to the long-term average.
-
Sandia/NREL methods : Re-rank top months by their closeness to long-term averages, filtering based on extreme values.
-
TMY : Combine the selected months into a continuous year, smoothing variable transitions at month boundaries.
Notes
Pay attention to the default return modus of this function ! Without any --verbose asked, is to print all TMY variable values as one CSV string. In the case of a TMY dataset, this is likely very long.
Source code in pvgisprototype/cli/meteo/tmy.py
def tmy(
longitude: Annotated[float, typer_argument_longitude_in_degrees] = float(),
latitude: Annotated[float, typer_argument_latitude_in_degrees] = float(),
# time_series_2: Annotated[Path, typer_option_time_series] = None,
timestamps: Annotated[DatetimeIndex, typer_argument_naive_timestamps] = str(
now_datetime()
),
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = None,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
convert_longitude_360: Annotated[bool, typer_option_convert_longitude_360] = False,
# Required variables
# time_series: Annotated[Path, typer_argument_time_series],
variable: Annotated[str | None, typer_option_data_variable] = None,
meteorological_variable: Annotated[
List[MeteorologicalVariable],
typer.Option(
help="Standard name of meteorological variable for Finkelstein-Schafer statistics"
),
] = [MeteorologicalVariable.all],
global_horizontal_irradiance: Annotated[
Path | None, typer_option_global_horizontal_irradiance
] = None,
direct_horizontal_irradiance: Annotated[
Path | None, typer_option_direct_horizontal_irradiance
] = None,
temperature_series: Annotated[
TemperatureSeries, typer_option_temperature_series_for_tmy
] = TemperatureSeries().average_air_temperature,
relative_humidity_series: Annotated[
RelativeHumiditySeries, typer_option_relative_humidity_series_for_tmy,
] = RelativeHumiditySeries().average_relative_humidity,
wind_speed_series: Annotated[
WindSpeedSeries, typer_option_wind_speed_series_for_tmy
] = WindSpeedSeries().average_wind_speed,
# wind_speed_variable: Annotated[str | None, typer_option_data_variable] = None,
# Solar positioning, required for the direct normal irradiance
solar_position_model: SolarPositionModel = SOLAR_POSITION_ALGORITHM_DEFAULT,
eccentricity_phase_offset: float = EccentricityPhaseOffset().value,
eccentricity_amplitude: float = EccentricityAmplitude().value,
# Series selection options
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = MethodForInexactMatches.nearest,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
# Output options
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
output_filename: Annotated[
Path, typer_option_output_filename
] = "series_in", # Path(),
variable_name_as_suffix: Annotated[
bool, typer_option_variable_name_as_suffix
] = True,
# Optios for internal calculations
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
# More output options
angle_output_units: Annotated[str, typer_option_angle_output_units] = RADIANS,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
weighting_scheme: TypicalMeteorologicalMonthWeightingScheme = TypicalMeteorologicalMonthWeightingScheme.ISO_15927_4.value,
plot_statistic: Annotated[
list[TMYStatisticModel],
typer.Option(help="Select which Finkelstein-Schafer statistics to plot"),
] = None, # [TMYStatisticModel.tmy.value],
limit_x_axis_to_tmy_extent: Annotated[
bool, "Limit plot of input time series to temporal extent of TMY"
] = True,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = QuickResponseCode.NoneValue,
metadata: Annotated[bool, typer_option_command_metadata] = False,
):
"""Generate the Typical Meteorological Year (TMY)
A typical meteorological year (TMY) is a set of meteorological data with
data values for every hour in a year for a given geographical location. The
data are selected from hourly data in a longer time period (normally 10
years or more).
An overview of the algorithm for calculating the TMY :
1. **Input data** : Read _at least_ 10 years of hourly time series over a
location
2. **Daily Statistics** : Compute daily maximum, minimum, and mean for
selected variables.
3. **Cumulative Distribution Function** (CDF) : Compute the CDF of each
variable for each month :
3.1 one CDF for each variable, month, and year. For example, for the
Global Horizontal Irradiance (GHI) : one for January 2011, one for
January 2012 and so on, _and_ for each month the same for the ambient
Temperature or other variables.
3.2 one long-term CDF for each variable across all years for each month.
4. **Finkelstein-Schafer Statistic** :
4.1 Compute the absolute difference between the long-term CDF and the
candidate month's CDF. 4.2 Compute the weighted sum (WS) of these
differences for each month and year.
5. **Typical Months** : Rank months by the lowest WS for each month (e.g.,
rank all Januaries).
6. **Month Selection** :
- ISO method: Select months based on wind speed similarity to the
long-term average.
- Sandia/NREL methods : Re-rank top months by their closeness to
long-term averages, filtering based on extreme values.
7. **TMY** : Combine the selected months into a continuous year, smoothing
variable transitions at month boundaries.
Notes
-----
Pay attention to the default _return_ modus of this function ! Without any
`--verbose` asked, is to print all TMY variable values as one CSV string.
In the case of a TMY dataset, this is likely very long.
"""
direct_normal_irradiance_series = None
direct_normal_irradiance = None
# Map variables to their data series
variable_series_map: Dict[MeteorologicalVariable, any] = {
MeteorologicalVariable.MEAN_DRY_BULB_TEMPERATURE: temperature_series,
MeteorologicalVariable.MEAN_RELATIVE_HUMIDITY: relative_humidity_series,
MeteorologicalVariable.MEAN_WIND_SPEED: wind_speed_series,
MeteorologicalVariable.GLOBAL_HORIZONTAL_IRRADIANCE: global_horizontal_irradiance,
MeteorologicalVariable.DIRECT_NORMAL_IRRADIANCE: direct_normal_irradiance,
}
# meteorological_variable = MeteorologicalVariable.MEAN_DRY_BULB_TEMPERATURE
meteorological_variables = select_meteorological_variables(
MeteorologicalVariable, meteorological_variable
) # Using a callback fails!
# Filter map to only variables requested
filtered_variable_map = {
var: data
for var, data in variable_series_map.items()
if var in meteorological_variables
}
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
transient=True,
) as progress:
progress.add_task(description="Calculating the TMY...", total=None)
if isinstance(global_horizontal_irradiance, (str, Path)) and isinstance(
direct_horizontal_irradiance, (str, Path)
):
global_horizontal_irradiance = select_time_series(
time_series=global_horizontal_irradiance,
# longitude=longitude_for_selection,
# latitude=latitude_for_selection,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
# convert_longitude_360=convert_longitude_360,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
verbose=0, # no verbosity here by choice!
log=log,
)
direct_horizontal_irradiance = select_time_series(
time_series=direct_horizontal_irradiance,
# longitude=longitude_for_selection,
# latitude=latitude_for_selection,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
# convert_longitude_360=convert_longitude_360,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
verbose=0, # no verbosity here by choice!
log=log,
)
direct_normal_irradiance_series = calculate_direct_normal_from_horizontal_irradiance_series(
direct_horizontal_irradiance=direct_horizontal_irradiance.values,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
# timezone=timezone,
solar_position_model=solar_position_model,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
# angle_output_units=angle_output_units,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
fingerprint=fingerprint,
)
direct_normal_irradiance_series = DataArray(
direct_normal_irradiance_series.value,
coords=[("time", timestamps)],
name=direct_normal_irradiance_series.title,
)
# direct_normal_irradiance_series.attrs["units"] = "W/m^2"
# direct_normal_irradiance_series.load()
if isinstance(temperature_series, Path):
temperature_series = select_time_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
time_series=temperature_series,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
# dtype=dtype,
# array_backend=array_backend,
# multi_thread=multi_thread,
verbose=verbose,
log=log,
)
if isinstance(relative_humidity_series, Path):
# relative_humidity_series = get_relative_humidity_series(
relative_humidity_series = select_time_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
time_series=relative_humidity_series,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
# dtype=dtype,
# array_backend=array_backend,
# multi_thread=multi_thread,
verbose=verbose,
log=log,
)
if isinstance(wind_speed_series, Path):
# wind_speed_series = get_wind_speed_series(
wind_speed_series = select_time_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
time_series=wind_speed_series,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
# dtype=dtype,
# array_backend=array_backend,
# multi_thread=multi_thread,
verbose=verbose,
log=log,
)
tmy = calculate_tmy(
meteorological_variables=meteorological_variables,
temperature_series=temperature_series,
relative_humidity_series=relative_humidity_series,
wind_speed_series=wind_speed_series,
# wind_speed_variable=wind_speed_variable,
global_horizontal_irradiance=global_horizontal_irradiance,
direct_normal_irradiance=direct_normal_irradiance_series,
timestamps=timestamps,
weighting_scheme=weighting_scheme,
verbose=verbose,
fingerprint=fingerprint,
)
if plot_statistic:
tmy_statistics = select_tmy_models(
enum_type=TMYStatisticModel,
models=plot_statistic,
)
plot_requested_tmy_statistics(
tmy_series=tmy,
variable=variable,
statistics=tmy_statistics,
meteorological_variables=meteorological_variables,
temperature_series=temperature_series,
relative_humidity_series=relative_humidity_series,
wind_speed_series=wind_speed_series,
global_horizontal_irradiance=global_horizontal_irradiance,
direct_normal_irradiance=direct_normal_irradiance_series,
weighting_scheme=weighting_scheme.name,
limit_x_axis_to_tmy_extent=limit_x_axis_to_tmy_extent,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if quick_response_code.value != QuickResponseCode.NoneValue:
print(f"[code]quick_response_code[/code] {NOT_IMPLEMENTED_CLI}")
# from pvgisprototype.cli.print.qr import print_quick_response_code
# print_quick_response_code(
# dictionary=tmy,
# longitude=longitude,
# latitude=latitude,
# elevation=False,
# surface_orientation=False,
# surface_tilt=False,
# timestamps=timestamps,
# rounding_places=rounding_places,
# )
if not quiet:
if verbose > 0:
print(f"[code]verbose[/code] {NOT_IMPLEMENTED_CLI}")
for meteorological_variable, output in tmy.items():
continue
else:
# When verbose=0, print TMY data as CSV
for meteorological_variable in meteorological_variables:
variable_output = tmy.get(meteorological_variable)
if variable_output is None:
continue
# Get the actual TMY DataArray, not the FS statistic
tmy_dataarray = retrieve_nested_value(variable_output, TMYStatisticModel.tmy.value)
if tmy_dataarray is not None:
# Flatten and print as CSV
flat_values = tmy_dataarray.values.flatten().astype(str)
csv_str = ",".join(flat_values)
print(csv_str)
# if not quiet:
# if verbose > 0:
# print(f"[code]verbose[/code] {NOT_IMPLEMENTED_CLI}")
# for meteorological_variable, output in tmy.items():
# continue
# # from pvgisprototype.cli.print.irradiance import print_irradiance_table_2
# # print_irradiance_table_2(
# # longitude=longitude,
# # latitude=latitude,
# # timestamps=timestamps,
# # dictionary=tmy,
# # # title=photovoltaic_power_output_series['Title'] + f" series {POWER_UNIT}",
# # rounding_places=rounding_places,
# # index=index,
# # surface_orientation=True,
# # surface_tilt=True,
# # verbose=verbose,
# # )
# else:
# flat_list = []
# for meteorological_variable in meteorological_variables:
# statistics = tmy.get(meteorological_variable)
# for data_array in statistics.get(
# FinkelsteinSchaferStatisticModel.ranked, NOT_AVAILABLE
# ):
# flat_list.extend(data_array.values.flatten().astype(str))
# csv_str = ",".join(flat_list)
# print(csv_str)
if statistics:
print(f"[code]statistics[/code] {NOT_IMPLEMENTED_CLI}")
# from pvgisprototype.api.series.statistics import print_series_statistics
# print_series_statistics(
# data_array=tmy,
# timestamps=timestamps,
# groupby=groupby,
# title="Typical Meteorological Year",
# )
if uniplot:
print(f"[code]uniplot[/code] {NOT_IMPLEMENTED_CLI}")
# from pvgisprototype.api.plot import uniplot_data_array_series
# uniplot_data_array_series(
# data_array=tmy[list(tmy.data_vars)[0]],
# # list_extra_data_arrays=individual_series,
# timestamps=timestamps,
# resample_large_series=resample_large_series,
# lines=True,
# supertitle="Typical Meteorological Year",
# title="Typical Meteorological Year",
# label="TMY",
# # extra_legend_labels=individual_labels,
# unit="?",
# terminal_width_fraction=terminal_width_fraction,
# )
if fingerprint:
print(f"[code]fingerprint[/code] {NOT_IMPLEMENTED_CLI}")
# from pvgisprototype.cli.print.fingerprint import print_finger_hash
# print_finger_hash(dictionary=tmy[list(tmy.data_vars)[0]])
if metadata:
import click
from pvgisprototype.cli.print.metadata import print_command_metadata
print_command_metadata(context=click.get_current_context())
# Call write_irradiance_csv() last : it modifies the input dictionary !
if csv:
print(f"[code]csv[/code] {NOT_IMPLEMENTED_CLI}")
tmy_weighting ¶
tmy_weighting(
meteorological_variable: Annotated[
MeteorologicalVariable,
Argument(
help="Standard name of meteorological variable for Finkelstein-Schafer statistics"
),
] = None,
weighting_scheme: Annotated[
TypicalMeteorologicalMonthWeightingScheme,
Option(help="Weighting scheme"),
] = ISO_15927_4.value,
)
Print the available weightings for a meteorological variable or full scheme.
Source code in pvgisprototype/cli/meteo/tmy.py
def tmy_weighting(
meteorological_variable: Annotated[
MeteorologicalVariable,
typer.Argument(help="Standard name of meteorological variable for Finkelstein-Schafer statistics"),
] = None,
weighting_scheme: Annotated[
TypicalMeteorologicalMonthWeightingScheme,
typer.Option(help="Weighting scheme"),
] = TypicalMeteorologicalMonthWeightingScheme.ISO_15927_4.value,
):
"""Print the available weightings for a meteorological variable or full scheme."""
if meteorological_variable:
meteorological_variables = select_meteorological_variables(
MeteorologicalVariable, [meteorological_variable]
)
else:
print(f"{weighting_scheme} :")
meteorological_variables = [None] # To handle printing the full scheme
for meteorological_variable in meteorological_variables:
print(
get_typical_meteorological_month_weighting_scheme(
weighting_scheme=weighting_scheme,
meteorological_variable=meteorological_variable,
)
)
performance ¶
Modules:
| Name | Description |
|---|---|
broadband | CLI module to calculate the photovoltaic power output over a |
broadband_multiple_surfaces | CLI module to calculate the photovoltaic power output over a |
introduction | |
spectral | |
spectral_effect | Calculate the spectral factor |
broadband ¶
CLI module to calculate the photovoltaic power output over a location for a period in time.
Functions:
| Name | Description |
|---|---|
photovoltaic_power_output_series | Estimate the photovoltaic power output for a location and a moment or period |
photovoltaic_power_output_series ¶
photovoltaic_power_output_series(
ctx: Context,
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
global_horizontal_irradiance: Annotated[
Path | None,
typer_option_global_horizontal_irradiance,
] = None,
direct_horizontal_irradiance: Annotated[
Path | None,
typer_option_direct_horizontal_irradiance,
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries,
typer_argument_spectral_factor_series,
] = SPECTRAL_FACTOR_DEFAULT,
temperature_series: Annotated[
TemperatureSeries, typer_option_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_option_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches | None,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor,
typer_option_linke_turbidity_factor_series,
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[
float | None, typer_option_albedo
] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel,
typer_option_solar_position_model,
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel,
typer_option_solar_incidence_model,
] = iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool,
typer_option_zero_negative_solar_incidence_angle,
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[
float, typer_option_solar_constant
] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
horizon_profile: Annotated[
DataArray | None, typer_option_horizon_profile
] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model
] = pvgis,
shading_states: Annotated[
List[ShadingState], typer_option_shading_state
] = [all],
photovoltaic_module: Annotated[
PhotovoltaicModuleModel,
typer_option_photovoltaic_module_model,
] = PHOTOVOLTAIC_MODULE_DEFAULT,
peak_power: Annotated[
float, typer_option_photovoltaic_module_peak_power
] = PEAK_POWER_DEFAULT,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel,
typer_option_pv_power_algorithm,
] = king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm,
typer_option_module_temperature_algorithm,
] = faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = RADIANS,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
analysis: Annotated[
bool, typer_option_analysis
] = ANALYSIS_FLAG_TRUE,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
nomenclature: Annotated[
bool, typer_option_nomenclature
] = NOMENCLATURE_FLAG_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
version: Annotated[
bool, typer_option_version
] = VERSION_FLAG_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = METADATA_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = NoneValue,
profile: Annotated[
bool, typer_option_profiling
] = cPROFILE_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
)
Estimate the photovoltaic power output for a location and a moment or period in time.
Estimate the photovoltaic power over a time series or an arbitrarily aggregated energy production of a PV system connected to the electricity grid (without battery storage) based on broadband solar irradiance, ambient temperature and wind speed.
Notes
The optional input parameters global_horizontal_irradiance and direct_horizontal_irradiance accept any Xarray-support data file format and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the functions that calculate the diffuse and direct components, are defined as global_horizontal_component and direct_horizontal_component. This is to avoid confusion at the function level. For example, the function calculate_diffuse_inclined_irradiance_series() can read the direct horizontal component (thus the name of it direct_horizontal_component as well as simulate it. The point is to make it clear that if the direct_horizontal_component parameter is True (which means the user has provided an external dataset), then read it using the select_time_series() function.
Source code in pvgisprototype/cli/performance/broadband.py
@log_function_call
def photovoltaic_power_output_series(
ctx: typer.Context,
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(Timestamp.now()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
global_horizontal_irradiance: Annotated[
Path | None, typer_option_global_horizontal_irradiance
] = None,
direct_horizontal_irradiance: Annotated[
Path | None, typer_option_direct_horizontal_irradiance
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries, typer_argument_spectral_factor_series
] = SPECTRAL_FACTOR_DEFAULT, # Accept also list of float values ?
temperature_series: Annotated[
TemperatureSeries, typer_option_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_option_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches | None, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor, typer_option_linke_turbidity_factor_series
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[float | None, typer_option_albedo] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel, typer_option_solar_position_model
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel, typer_option_solar_incidence_model
] = SolarIncidenceModel.iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool, typer_option_zero_negative_solar_incidence_angle
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[float, typer_option_solar_constant] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
horizon_profile: Annotated[DataArray | None, typer_option_horizon_profile] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model] = ShadingModel.pvgis, # for power generation : should be one !
shading_states: Annotated[
List[ShadingState], typer_option_shading_state] = [ShadingState.all],
photovoltaic_module: Annotated[
PhotovoltaicModuleModel, typer_option_photovoltaic_module_model
] = PHOTOVOLTAIC_MODULE_DEFAULT, # PhotovoltaicModuleModel.CSI_FREE_STANDING,
peak_power: Annotated[float, typer_option_photovoltaic_module_peak_power] = PEAK_POWER_DEFAULT,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel, typer_option_pv_power_algorithm
] = PhotovoltaicModulePerformanceModel.king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm, typer_option_module_temperature_algorithm
] = ModuleTemperatureAlgorithm.faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
angle_output_units: Annotated[str, typer_option_angle_output_units] = RADIANS,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
analysis: Annotated[bool, typer_option_analysis] = ANALYSIS_FLAG_TRUE,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
nomenclature: Annotated[
bool, typer_option_nomenclature
] = NOMENCLATURE_FLAG_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
version: Annotated[bool, typer_option_version] = VERSION_FLAG_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = METADATA_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = QuickResponseCode.NoneValue,
profile: Annotated[bool, typer_option_profiling] = cPROFILE_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
):
"""Estimate the photovoltaic power output for a location and a moment or period
in time.
Estimate the photovoltaic power over a time series or an arbitrarily
aggregated energy production of a PV system connected to the electricity
grid (without battery storage) based on broadband solar irradiance, ambient
temperature and wind speed.
Notes
-----
The optional input parameters `global_horizontal_irradiance` and
`direct_horizontal_irradiance` accept any Xarray-support data file format
and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the
functions that calculate the diffuse and direct components, are defined as
`global_horizontal_component` and `direct_horizontal_component`. This is to
avoid confusion at the function level. For example, the function
`calculate_diffuse_inclined_irradiance_series()` can read the direct
horizontal component (thus the name of it `direct_horizontal_component` as
well as simulate it. The point is to make it clear that if the
`direct_horizontal_component` parameter is True (which means the user has
provided an external dataset), then read it using the
`select_time_series()` function.
"""
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
transient=True,
) as progress:
progress.add_task(description="Calculating photovoltaic power output...", total=None)
if isinstance(global_horizontal_irradiance, (str, Path)) and isinstance(
direct_horizontal_irradiance, (str, Path)
): # NOTE This is in the case everything is pathlike
global_horizontal_irradiance_array, direct_horizontal_irradiance_array = (
read_horizontal_irradiance_components_from_sarah(
shortwave=global_horizontal_irradiance,
direct=direct_horizontal_irradiance,
longitude=convert_float_to_degrees_if_requested(longitude, DEGREES),
latitude=convert_float_to_degrees_if_requested(latitude, DEGREES),
timestamps=timestamps,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
multi_thread=multi_thread,
# multi_thread=False,
verbose=verbose,
log=log,
)
)
else: # Ensure the calculate() function below receices an array or None !
global_horizontal_irradiance_array = None
direct_horizontal_irradiance_array = None
temperature_series, wind_speed_series, spectral_factor_series = get_time_series(
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
spectral_factor_series=spectral_factor_series,
timestamps=timestamps,
longitude=Longitude(value=longitude, unit='radians'),
latitude=Latitude(values=latitude, units='radians'),
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
dtype=dtype,
array_backend=array_backend,
multi_thread=multi_thread,
verbose=verbose,
log=log,
)
photovoltaic_power_output_series = calculate_photovoltaic_power_output_series(
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=surface_orientation,
surface_tilt=surface_tilt,
timestamps=timestamps,
timezone=timezone,
global_horizontal_irradiance=global_horizontal_irradiance_array,
direct_horizontal_irradiance=direct_horizontal_irradiance_array,
spectral_factor_series=spectral_factor_series,
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
linke_turbidity_factor_series=linke_turbidity_factor_series,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
# unrefracted_solar_zenith=unrefracted_solar_zenith,
albedo=albedo,
apply_reflectivity_factor=apply_reflectivity_factor,
solar_position_model=solar_position_model,
solar_incidence_model=solar_incidence_model,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
horizon_profile=horizon_profile,
shading_model=shading_model,
shading_states=shading_states,
solar_time_model=solar_time_model,
solar_constant=solar_constant,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
# angle_output_units=angle_output_units,
photovoltaic_module=photovoltaic_module,
peak_power=peak_power,
system_efficiency=system_efficiency,
power_model=power_model,
temperature_model=temperature_model,
efficiency=efficiency,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
fingerprint=fingerprint,
profile=profile,
validate_output=validate_output,
) # Re-Design Me ! ------------------------------------------------
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if quick_response_code.value != QuickResponseCode.NoneValue:
from pvgisprototype.cli.print.qr import print_quick_response_code
print_quick_response_code(
dictionary=photovoltaic_power_output_series.output,
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=True,
surface_tilt=True,
timestamps=timestamps,
rounding_places=rounding_places,
output_type=quick_response_code,
)
return
if not quiet:
if verbose > 0:
from pvgisprototype.cli.print.irradiance.data import print_irradiance_table_2
print_irradiance_table_2(
# title=photovoltaic_power_output_series['Title'] + f" series {POWER_UNIT}",
irradiance_data=photovoltaic_power_output_series.output,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
rounding_places=rounding_places,
index=index,
surface_orientation=True,
surface_tilt=True,
verbose=verbose,
)
else:
# ------------------------- Better handling of rounding vs dtype ?
print(
",".join(
# round_float_values(
# photovoltaic_power_output_series.value.flatten(),
# rounding_places,
# ).astype(str)
photovoltaic_power_output_series.value.flatten().astype(str)
)
)
if statistics:
from pvgisprototype.cli.print.series import print_series_statistics
print_series_statistics(
data_array=photovoltaic_power_output_series.value,
timestamps=timestamps,
groupby=groupby,
title="Photovoltaic power output",
rounding_places=rounding_places,
)
if analysis:
from pvgisprototype.cli.print.performance.analysis import print_change_percentages_panel
print_change_percentages_panel(
photovoltaic_power=photovoltaic_power_output_series,#.output,
longitude=longitude,
latitude=latitude,
elevation=elevation,
timestamps=timestamps,
timezone=timezone,
rounding_places=1, # minimalism
index=index,
surface_orientation=True,
surface_tilt=True,
horizon_profile=horizon_profile,
version=version,
fingerprint=fingerprint,
verbose=verbose,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_data_array_series
uniplot_data_array_series(
data_array=photovoltaic_power_output_series.value,
list_extra_data_arrays=None,
timestamps=timestamps,
resample_large_series=resample_large_series,
lines=True,
supertitle=photovoltaic_power_output_series.supertitle,
title=photovoltaic_power_output_series.title,
label=photovoltaic_power_output_series.label,
extra_legend_labels=None,
unit=POWER_UNIT,
terminal_width_fraction=terminal_width_fraction,
)
if metadata:
import click
from pvgisprototype.cli.print.metadata import print_command_metadata
print_command_metadata(context=click.get_current_context())
if fingerprint and not analysis:
from pvgisprototype.cli.print.fingerprint import print_finger_hash
print_finger_hash(dictionary=photovoltaic_power_output_series.output)
# Call write_irradiance_csv() last : it modifies the input dictionary !
if csv:
from pvgisprototype.cli.write import write_irradiance_csv
write_irradiance_csv(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dictionary=photovoltaic_power_output_series.output,
filename=csv,
index=index,
)
broadband_multiple_surfaces ¶
CLI module to calculate the photovoltaic power output over a location for a period in time.
Functions:
| Name | Description |
|---|---|
photovoltaic_power_output_series_from_multiple_surfaces | Estimate the sum of photovoltaic output for multiple solar surface |
photovoltaic_power_output_series_from_multiple_surfaces ¶
photovoltaic_power_output_series_from_multiple_surfaces(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
list | None, typer_option_surface_orientation_multi
] = [float(SURFACE_ORIENTATION_DEFAULT)],
surface_tilt: Annotated[
list | None, typer_option_surface_tilt_multi
] = [float(SURFACE_TILT_DEFAULT)],
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(DatetimeIndex([now()])),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
global_horizontal_irradiance: Annotated[
Path | None,
typer_option_global_horizontal_irradiance,
] = None,
direct_horizontal_irradiance: Annotated[
Path | None,
typer_option_direct_horizontal_irradiance,
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries,
typer_argument_spectral_factor_series,
] = SPECTRAL_FACTOR_DEFAULT,
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor,
typer_option_linke_turbidity_factor_series,
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[
float | None, typer_option_albedo
] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel,
typer_option_solar_position_model,
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel,
typer_option_solar_incidence_model,
] = iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool,
typer_option_zero_negative_solar_incidence_angle,
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
horizon_profile: Annotated[
DataArray | None, typer_option_horizon_profile
] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model
] = pvgis,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[
float, typer_option_solar_constant
] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = RADIANS,
photovoltaic_module: Annotated[
PhotovoltaicModuleModel,
typer_option_photovoltaic_module_model,
] = PHOTOVOLTAIC_MODULE_DEFAULT,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel,
typer_option_pv_power_algorithm,
] = king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm,
typer_option_module_temperature_algorithm,
] = faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
analysis: Annotated[
bool, typer_option_analysis
] = ANALYSIS_FLAG_TRUE,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = NoneValue,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
profile: Annotated[
bool, typer_option_profiling
] = cPROFILE_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
)
Estimate the sum of photovoltaic output for multiple solar surface setups for a location and a moment or period in time.
Estimate the sum of photovoltaic power over a time series or an arbitrarily aggregated energy production of multiple PV system setups, i.e. different tilts and orientations, connected to the electricity grid (without battery storage) based on broadband solar irradiance, ambient temperature and wind speed.
Notes
The optional input parameters global_horizontal_irradiance and direct_horizontal_irradiance accept any Xarray-support data file format and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the functions that calculate the diffuse and direct presentation, are defined as global_horizontal_component and direct_horizontal_component. This is to avoid confusion at the function level. For example, the function calculate_diffuse_inclined_irradiance_series() can read the direct horizontal component (thus the name of it direct_horizontal_component as well as simulate it. The point is to make it clear that if the direct_horizontal_component parameter is True (which means the user has provided an external dataset), then read it using the select_time_series() function.
Source code in pvgisprototype/cli/performance/broadband_multiple_surfaces.py
@log_function_call
def photovoltaic_power_output_series_from_multiple_surfaces(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
list | None, typer_option_surface_orientation_multi
] = [float(SURFACE_ORIENTATION_DEFAULT)],
surface_tilt: Annotated[list | None, typer_option_surface_tilt_multi] = [
float(SURFACE_TILT_DEFAULT)
],
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(DatetimeIndex([Timestamp.now()])),
start_time: Annotated[datetime | None, typer_option_start_time] = None,
periods: Annotated[int | None, typer_option_periods] = None,
frequency: Annotated[str | None, typer_option_frequency] = None,
end_time: Annotated[datetime | None, typer_option_end_time] = None,
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
global_horizontal_irradiance: Annotated[
Path | None, typer_option_global_horizontal_irradiance
] = None,
direct_horizontal_irradiance: Annotated[
Path | None, typer_option_direct_horizontal_irradiance
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries, typer_argument_spectral_factor_series
] = SPECTRAL_FACTOR_DEFAULT, # Accept also list of float values ?
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor, typer_option_linke_turbidity_factor_series
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[float | None, typer_option_albedo] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel, typer_option_solar_position_model
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel, typer_option_solar_incidence_model
] = SolarIncidenceModel.iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool, typer_option_zero_negative_solar_incidence_angle
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
horizon_profile: Annotated[DataArray | None, typer_option_horizon_profile] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model] = ShadingModel.pvgis, # for power generation : should be one !
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[float, typer_option_solar_constant] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
angle_output_units: Annotated[str, typer_option_angle_output_units] = RADIANS,
photovoltaic_module: Annotated[
PhotovoltaicModuleModel, typer_option_photovoltaic_module_model
] = PHOTOVOLTAIC_MODULE_DEFAULT, # PhotovoltaicModuleModel.CSI_FREE_STANDING,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel, typer_option_pv_power_algorithm
] = PhotovoltaicModulePerformanceModel.king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm, typer_option_module_temperature_algorithm
] = ModuleTemperatureAlgorithm.faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
analysis: Annotated[bool, typer_option_analysis] = ANALYSIS_FLAG_TRUE,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = QuickResponseCode.NoneValue,
metadata: Annotated[bool, typer_option_command_metadata] = False,
profile: Annotated[bool, typer_option_profiling] = cPROFILE_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
):
"""Estimate the sum of photovoltaic output for multiple solar surface
setups for a location and a moment or period in time.
Estimate the sum of photovoltaic power over a time series or an arbitrarily
aggregated energy production of multiple PV system setups, i.e. different
tilts and orientations, connected to the electricity grid (without battery
storage) based on broadband solar irradiance, ambient temperature and wind
speed.
Notes
-----
The optional input parameters `global_horizontal_irradiance` and
`direct_horizontal_irradiance` accept any Xarray-support data file format
and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the
functions that calculate the diffuse and direct presentation, are defined as
`global_horizontal_component` and `direct_horizontal_component`. This is to
avoid confusion at the function level. For example, the function
`calculate_diffuse_inclined_irradiance_series()` can read the direct
horizontal component (thus the name of it `direct_horizontal_component` as
well as simulate it. The point is to make it clear that if the
`direct_horizontal_component` parameter is True (which means the user has
provided an external dataset), then read it using the
`select_time_series()` function.
"""
if len(surface_tilt) != len(surface_orientation):
from pvgisprototype.api.series.hardcodings import exclamation_mark
logger.error(
f"{exclamation_mark} Aborting as length of --surface_orientation and --surface_tilt is not the same!",
alt=f"{exclamation_mark} [red]Aborting[/red] as [red]length[/red] [code]--surface-orientation[/code] and [code]--surface-tilt[/code] [red]is not the same[/red]!",
)
return
photovoltaic_power_output_series = calculate_photovoltaic_power_output_series_from_multiple_surfaces(
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=surface_orientation,
surface_tilt=surface_tilt,
timestamps=timestamps,
timezone=timezone,
global_horizontal_irradiance=global_horizontal_irradiance,
direct_horizontal_irradiance=direct_horizontal_irradiance,
spectral_factor_series=spectral_factor_series,
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
dtype=dtype,
array_backend=array_backend,
multi_thread=multi_thread,
linke_turbidity_factor_series=linke_turbidity_factor_series,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
# unrefracted_solar_zenith=unrefracted_solar_zenith,
albedo=albedo,
apply_reflectivity_factor=apply_reflectivity_factor,
solar_position_model=solar_position_model,
solar_incidence_model=solar_incidence_model,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
horizon_height=horizon_profile, # Review naming please ?
horizon_profile=horizon_profile,
shading_model=shading_model,
shading_states=shading_states,
solar_time_model=solar_time_model,
solar_constant=solar_constant,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
angle_output_units=angle_output_units,
photovoltaic_module=photovoltaic_module,
system_efficiency=system_efficiency,
power_model=power_model,
temperature_model=temperature_model,
efficiency=efficiency,
verbose=verbose,
log=log,
fingerprint=fingerprint,
profile=profile,
validate_output=validate_output,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if quick_response_code.value != QuickResponseCode.NoneValue:
from pvgisprototype.cli.print.qr import print_quick_response_code
print_quick_response_code(
dictionary=photovoltaic_power_output_series.presentation,
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=True,
surface_tilt=True,
timestamps=timestamps,
rounding_places=rounding_places,
)
return
if not quiet:
if verbose > 0:
from pvgisprototype.cli.print.irradiance.data import print_irradiance_table_2
print_irradiance_table_2(
title=photovoltaic_power_output_series.title + f" series {POWER_UNIT}",
irradiance_data=photovoltaic_power_output_series.presentation,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
rounding_places=rounding_places,
index=index,
surface_orientation=True,
surface_tilt=True,
verbose=verbose,
)
else:
flat_list = photovoltaic_power_output_series.series.flatten().astype(str)
csv_str = ",".join(flat_list)
print(csv_str)
if statistics:
from pvgisprototype.cli.print.series import print_series_statistics
print_series_statistics(
data_array=photovoltaic_power_output_series.value,
timestamps=timestamps,
groupby=groupby,
title=photovoltaic_power_output_series.title,
)
if analysis:
from pvgisprototype.cli.print.performance.anaysis import print_change_percentages_panel
print_change_percentages_panel(
longitude=longitude,
latitude=latitude,
elevation=elevation,
timestamps=timestamps,
dictionary=photovoltaic_power_output_series.presentation,
# title=photovoltaic_power_output_series['Title'] + f" series {POWER_UNIT}",
rounding_places=1, # minimalism
index=index,
surface_orientation=True,
surface_tilt=True,
fingerprint=fingerprint,
verbose=verbose,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_data_array_series
individual_series = [
series.value
for series in photovoltaic_power_output_series.individual_series
]
surface_orientation = [
convert_float_to_degrees_if_requested(orientation, angle_output_units)
for orientation in surface_orientation
]
surface_tilt = [
convert_float_to_degrees_if_requested(tilt, angle_output_units)
for tilt in surface_tilt
]
surface_orientation = round_float_values(surface_orientation, rounding_places)
surface_tilt = round_float_values(surface_tilt, rounding_places)
individual_labels = [
f"Orientation, Tilt : {orientation}°, {tilt}°"
for orientation, tilt in zip(surface_orientation, surface_tilt)
]
uniplot_data_array_series(
data_array=photovoltaic_power_output_series.series,
list_extra_data_arrays=individual_series,
timestamps=timestamps,
resample_large_series=resample_large_series,
lines=True,
supertitle=photovoltaic_power_output_series.supertitle,
title=photovoltaic_power_output_series.title,
label=photovoltaic_power_output_series.label,
extra_legend_labels=individual_labels,
unit=POWER_UNIT,
terminal_width_fraction=terminal_width_fraction,
)
if fingerprint and not analysis:
from pvgisprototype.cli.print.fingerprint import print_finger_hash
print_finger_hash(dictionary=photovoltaic_power_output_series.presentation)
if metadata:
import click
from pvgisprototype.cli.print.metadata import print_command_metadata
print_command_metadata(context=click.get_current_context())
# Call write_irradiance_csv() last : it modifies the input dictionary !
if csv:
from pvgisprototype.cli.write import write_irradiance_csv
write_irradiance_csv(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dictionary=photovoltaic_power_output_series.presentation,
filename=csv,
index=index,
)
introduction ¶
Functions:
| Name | Description |
|---|---|
photovoltaic_performance_introduction | A short introduction on photovoltaic performance |
photovoltaic_performance_introduction ¶
A short introduction on photovoltaic performance
Source code in pvgisprototype/cli/performance/introduction.py
def photovoltaic_performance_introduction():
"""A short introduction on photovoltaic performance"""
introduction = """
[underline]The performance of a photovoltaic (PV) system[/underline] is ...
"""
note = """
PVGIS can estimate the performance of a series of photovoltaic technologies using either [magenta]broadband[/magenta] or [magenta]spectrally resolved[/magenta] irradiance data.
"""
from rich.panel import Panel
note_in_a_panel = Panel(
"[italic]{}[/italic]".format(note),
title="[bold cyan]Note[/bold cyan]",
width=78,
)
from rich.console import Console
console = Console()
# introduction.wrap(console, 30)
console.print(introduction)
console.print(note_in_a_panel)
console.print(A_PRIMER_ON_PHOTOVOLTAIC_PERFORMANCE)
spectral ¶
Functions:
| Name | Description |
|---|---|
spectral_photovoltaic_performance_analysis | This method accounts for the effects of the solar spectrum's varying |
spectral_photovoltaic_performance_analysis ¶
spectral_photovoltaic_performance_analysis(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None, typer_option_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_option_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
str | None, typer_option_timezone
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
spectrally_resolved_global_horizontal_irradiance_series: Annotated[
Path | None,
typer_option_global_horizontal_irradiance,
] = None,
spectrally_resolved_direct_horizontal_irradiance_series: Annotated[
Path | None,
typer_option_direct_horizontal_irradiance,
] = None,
number_of_junctions: int = 1,
spectral_response_data: Path | None = None,
standard_conditions_response: Path | None = None,
minimum_spectral_mismatch=MINIMUM_SPECTRAL_MISMATCH,
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor,
typer_option_linke_turbidity_factor_series,
] = None,
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[
float | None, typer_option_albedo
] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = True,
solar_position_model: Annotated[
SolarPositionModel,
typer_option_solar_position_model,
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel,
typer_option_solar_incidence_model,
] = iqbal,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[
float, typer_option_solar_constant
] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
time_output_units: Annotated[
str, typer_option_time_output_units
] = MINUTES,
angle_units: Annotated[
str, typer_option_angle_units
] = RADIANS,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = RADIANS,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel,
typer_option_pv_power_algorithm,
] = king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm,
typer_option_module_temperature_algorithm,
] = faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
)
This method accounts for the effects of the solar spectrum's varying wavelengths on PV output, offering a more detailed analysis for systems sensitive to specific spectral ranges.
Source code in pvgisprototype/cli/performance/spectral.py
def spectral_photovoltaic_performance_analysis(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None, typer_option_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_option_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[datetime | None, typer_option_start_time] = None,
periods: Annotated[int | None, typer_option_periods] = None,
frequency: Annotated[str | None, typer_option_frequency] = None,
end_time: Annotated[datetime | None, typer_option_end_time] = None,
timezone: Annotated[str | None, typer_option_timezone] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
spectrally_resolved_global_horizontal_irradiance_series: Annotated[
Path | None, typer_option_global_horizontal_irradiance
] = None,
spectrally_resolved_direct_horizontal_irradiance_series: Annotated[
Path | None, typer_option_direct_horizontal_irradiance
] = None,
number_of_junctions: int = 1,
spectral_response_data: Path | None = None,
standard_conditions_response: Path | None = None, #: float = 1, # STCresponse : read from external data
# extraterrestrial_normal_irradiance_series, # spectral_ext,
minimum_spectral_mismatch=MINIMUM_SPECTRAL_MISMATCH,
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
# dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
# array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
# multi_thread: Annotated[bool, typer_option_multi_thread] = MULTI_THREAD_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor, typer_option_linke_turbidity_factor_series
] = None, # Changed this to np.ndarray
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[float | None, typer_option_albedo] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = True,
solar_position_model: Annotated[
SolarPositionModel, typer_option_solar_position_model
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel, typer_option_solar_incidence_model
] = SolarIncidenceModel.iqbal,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[float, typer_option_solar_constant] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
time_output_units: Annotated[str, typer_option_time_output_units] = MINUTES,
angle_units: Annotated[str, typer_option_angle_units] = RADIANS,
angle_output_units: Annotated[str, typer_option_angle_output_units] = RADIANS,
# horizon_heights: Annotated[List[float], typer.Argument(help="Array of horizon elevations.")] = None,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel, typer_option_pv_power_algorithm
] = PhotovoltaicModulePerformanceModel.king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm, typer_option_module_temperature_algorithm
] = ModuleTemperatureAlgorithm.faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
):
"""
This method accounts for the effects of the solar spectrum's varying
wavelengths on PV output, offering a more detailed analysis for systems
sensitive to specific spectral ranges.
"""
(
spectrally_resolved_photovoltaic_power,
results,
title,
) = calculate_spectral_photovoltaic_power_output(
longitude=longitude,
latitude=latitude,
elevation=elevation,
timestamps=timestamps,
timezone=timezone,
spectrally_resolved_global_horizontal_irradiance_series=spectrally_resolved_global_horizontal_irradiance_series,
spectrally_resolved_direct_horizontal_irradiance_series=spectrally_resolved_direct_horizontal_irradiance_series,
spectral_response_data=spectral_response_data,
number_of_junctions=number_of_junctions,
standard_conditions_response=standard_conditions_response,
minimum_spectral_mismatch=minimum_spectral_mismatch,
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
mask_and_scale=mask_and_scale,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
in_memory=in_memory,
surface_orientation=surface_orientation,
surface_tilt=surface_tilt,
linke_turbidity_factor_series=linke_turbidity_factor_series,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
unrefracted_solar_zenith=unrefracted_solar_zenith,
albedo=albedo,
apply_reflectivity_factor=apply_reflectivity_factor,
solar_position_model=solar_position_model,
solar_incidence_model=solar_incidence_model,
solar_time_model=solar_time_model,
solar_constant=solar_constant,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
time_output_units=time_output_units,
angle_units=angle_units,
angle_output_units=angle_output_units,
system_efficiency=system_efficiency,
power_model=power_model,
temperature_model=temperature_model,
efficiency=efficiency,
verbose=verbose,
)
# longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
# latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if not quiet:
if verbose > 0:
pass
# print_irradiance_table_2(
# longitude=longitude,
# latitude=latitude,
# timestamps=timestamps,
# dictionary=results,
# title=title + f' irradiance series {IRRADIANCE_UNIT}',
# rounding_places=rounding_places,
# index=index,
# verbose=verbose,
# )
else:
flat_list = spectrally_resolved_photovoltaic_power.flatten().astype(str)
csv_str = ",".join(flat_list)
print(csv_str)
spectral_effect ¶
Calculate the spectral factor
Functions:
| Name | Description |
|---|---|
spectral_factor | |
spectral_factor ¶
spectral_factor(
irradiance: Annotated[
Path, typer_argument_spectrally_resolved_irradiance
],
longitude: Annotated[
float, typer_argument_longitude_in_degrees
],
latitude: Annotated[
float, typer_argument_latitude_in_degrees
],
elevation: Annotated[float, typer_argument_elevation],
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
str | None, typer_option_timezone
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
responsivity: Annotated[
SpectralResponsivity,
typer_option_spectral_responsivity_pandas,
] = SPECTRAL_RESPONSIVITY_DATA,
integrate_responsivity: Annotated[
bool, typer_option_integrate_spectral_responsivity
] = False,
responsivity_column: Annotated[
str, typer_option_responsivity_column_name
] = SPECTRAL_RESPONSIVITY_CSV_COLUMN_NAME_DEFAULT,
wavelength_column: Annotated[
str, typer_option_wavelength_column_name
] = WAVELENGTHS_CSV_COLUMN_NAME_DEFAULT,
photovoltaic_module_type: Annotated[
List[PhotovoltaicModuleSpectralResponsivityModel],
typer_option_photovoltaic_module_type,
] = [cSi],
spectrally_resolved_irradiance: Annotated[
str, typer_option_data_variable
] = "",
average_irradiance_density: Annotated[
str, typer_option_data_variable
] = "",
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
limit_spectral_range: Annotated[
bool,
Option(
help="Limit the spectral range of the irradiance input data. Default for `spectral_factor_model = Pelland`"
),
] = False,
min_wavelength: Annotated[
float,
typer_option_minimum_spectral_irradiance_wavelength,
] = MIN_WAVELENGTH,
max_wavelength: Annotated[
float,
typer_option_maximum_spectral_irradiance_wavelength,
] = MAX_WAVELENGTH,
reference_spectrum: Annotated[
None | DataFrame, typer_option_reference_spectrum
] = None,
integrate_reference_spectrum: Annotated[
bool, typer_option_integrate_reference_spectrum
] = False,
spectral_factor_model: Annotated[
List[SpectralMismatchModel],
typer_option_spectral_factor_model,
] = [pvlib],
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
analysis: Annotated[
bool, typer_option_analysis
] = ANALYSIS_FLAG_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
show_footer: Annotated[
bool, Option(help="Show output table footer")
] = True,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = METADATA_FLAG_DEFAULT,
)
Source code in pvgisprototype/cli/performance/spectral_effect.py
@log_function_call
def spectral_factor(
irradiance: Annotated[
Path,
typer_argument_spectrally_resolved_irradiance,
],
longitude: Annotated[float, typer_argument_longitude_in_degrees],
latitude: Annotated[float, typer_argument_latitude_in_degrees],
elevation: Annotated[float, typer_argument_elevation],
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[str | None, typer_option_timezone] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
responsivity: Annotated[
SpectralResponsivity,
typer_option_spectral_responsivity_pandas,
] = SPECTRAL_RESPONSIVITY_DATA, # Accept also list of float values ?
integrate_responsivity: Annotated[
bool,
typer_option_integrate_spectral_responsivity,
] = False,
responsivity_column: Annotated[
str,
typer_option_responsivity_column_name,
] = SPECTRAL_RESPONSIVITY_CSV_COLUMN_NAME_DEFAULT,
wavelength_column: Annotated[
str,
typer_option_wavelength_column_name,
] = WAVELENGTHS_CSV_COLUMN_NAME_DEFAULT,
photovoltaic_module_type: Annotated[
List[PhotovoltaicModuleSpectralResponsivityModel],
typer_option_photovoltaic_module_type,
] = [PhotovoltaicModuleSpectralResponsivityModel.cSi],
spectrally_resolved_irradiance: Annotated[str, typer_option_data_variable] = "",
average_irradiance_density: Annotated[str, typer_option_data_variable] = "",
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
limit_spectral_range: Annotated[
bool,
typer.Option(
help="Limit the spectral range of the irradiance input data. Default for `spectral_factor_model = Pelland`"
),
] = False,
min_wavelength: Annotated[
float, typer_option_minimum_spectral_irradiance_wavelength
] = MIN_WAVELENGTH,
max_wavelength: Annotated[
float, typer_option_maximum_spectral_irradiance_wavelength
] = MAX_WAVELENGTH,
reference_spectrum: Annotated[
None | DataFrame,
typer_option_reference_spectrum,
] = None, # AM15G_IEC60904_3_ED4,
integrate_reference_spectrum: Annotated[
bool,
typer_option_integrate_reference_spectrum,
] = False,
spectral_factor_model: Annotated[
List[SpectralMismatchModel], typer_option_spectral_factor_model
] = [SpectralMismatchModel.pvlib],
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
analysis: Annotated[bool, typer_option_analysis] = ANALYSIS_FLAG_DEFAULT,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
show_footer: Annotated[bool, typer.Option(help="Show output table footer")] = True,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = METADATA_FLAG_DEFAULT,
):
""" """
# Ugly Hacks ! -----------------------------------------------------------
from pvgisprototype.api.position.models import select_models
photovoltaic_module_type = select_models(
PhotovoltaicModuleSpectralResponsivityModel, photovoltaic_module_type
) # Using a callback fails!
# ------------------------------------------------------------------------
def is_netcdf(file_path: Path) -> bool:
return file_path.suffix in {".nc", ".netcdf"}
if is_netcdf(irradiance):
# irradiance = (
# select_time_series(
# time_series=irradiance,
# longitude=longitude,
# latitude=latitude,
# timestamps=timestamps,
# neighbor_lookup=neighbor_lookup,
# tolerance=tolerance,
# mask_and_scale=mask_and_scale,
# in_memory=in_memory,
# verbose=verbose,
# log=log,
# )
# .to_numpy()
# .astype(dtype=dtype)
# )
spectrally_resolved_irradiance = select_time_series(
time_series=irradiance,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
variable=spectrally_resolved_irradiance,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
verbose=verbose,
log=log,
)
if (
SpectralMismatchModel.pvlib in spectral_factor_model
):
logger.debug(
f"Average irradiance density :\n{average_irradiance_density}",
alt=f"[bold]Average irradiance density[/bold] :\n{average_irradiance_density}",
)
if average_irradiance_density:
average_irradiance_density = select_time_series(
time_series=irradiance,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
variable=average_irradiance_density,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
verbose=verbose,
log=log,
)
# # Ugly Hack! --------------------------------------------------- #
# if not isinstance(average_irradiance_density, DataArray) and isinstance(spectrally_resolved_irradiance, DataArray):
# spectrally_resolved_irradiance = spectrally_resolved_irradiance.rename(
# {wavelength_column: "wavelength"}
# )
# # Ugly Hack! --------------------------------------------------- #
if limit_spectral_range:
import numpy
if numpy.any(
numpy.logical_or(
irradiance[wavelength_column] < min_wavelength,
irradiance[wavelength_column] > max_wavelength,
)
):
logger.debug(
f"{check_mark} The input irradiance wavelengths are within the reference range [{min_wavelength}, {max_wavelength}]."
)
else:
logger.warning(
f"{x_mark} The input irradiance wavelengths exceed the reference range [{min_wavelength}, {max_wavelength}]. Filtering..."
)
irradiance = irradiance.sel(
center_wavelength=numpy.logical_and(
irradiance[wavelength_column] > min_wavelength,
irradiance[wavelength_column] < max_wavelength,
)
)
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
transient=True,
) as progress:
progress.add_task(description="Calculating spectral factor...", total=None)
spectral_factor_series = calculate_spectral_factor(
longitude=longitude,
latitude=latitude,
elevation=elevation,
timestamps=timestamps,
timezone=timezone,
irradiance=spectrally_resolved_irradiance,
average_irradiance_density=average_irradiance_density,
responsivity=responsivity,
photovoltaic_module_type=photovoltaic_module_type,
reference_spectrum=reference_spectrum,
integrate_reference_spectrum=integrate_reference_spectrum,
spectral_factor_models=spectral_factor_model,
verbose=verbose,
log=log,
fingerprint=fingerprint,
)
# longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
# latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if not quiet:
if verbose > 0:
from pvgisprototype.cli.print.spectral_factor import print_spectral_factor
print_spectral_factor(
timestamps=timestamps,
spectral_factor_container=spectral_factor_series.components,
spectral_factor_model=spectral_factor_model,
photovoltaic_module_type=photovoltaic_module_type,
# include_statistics=statistics,
title="Spectral Factor",
verbose=verbose,
index=index,
show_footer=show_footer,
)
else:
spectral_factor_dictionary = {}
for model in spectral_factor_model:
for module_type in photovoltaic_module_type:
mismatch_data = (
spectral_factor_series.components.get(model)
.get(module_type)
.get(SPECTRAL_FACTOR_COLUMN_NAME)
)
if isinstance(mismatch_data, memoryview):
import numpy
mismatch_data = (numpy.array(mismatch_data).values.flatten(),)
# # ------------------------- Better handling of rounding vs dtype ?
# from pvgisprototype.api.utilities.conversions import round_float_values
# mismatch_data = round_float_values(
# mismatch_data,
# rounding_places,
# ).astype(str)
# # ------------------------- Better handling of rounding vs dtype ?
spectral_factor_dictionary[module_type.name] = mismatch_data
header = ", ".join(spectral_factor_dictionary.keys())
print(header)
# mismatch_values = ", ".join(mismatch_data.astype(str))
# print(f'{module_type.name}, {mismatch_values}')
# maximum length of mismatch data to properly format rows
max_length = max([len(v) for v in spectral_factor_dictionary.values()])
# Print each row of mismatch values, transposing the values
for i in range(max_length):
row = []
for module_type in spectral_factor_dictionary:
if i < len(spectral_factor_dictionary[module_type]):
row.append(
f"{spectral_factor_dictionary[module_type][i]:.6f}"
)
else:
row.append("") # Handle cases where lengths are uneven
print(", ".join(row))
if csv:
from pvgisprototype.cli.write import write_spectral_factor_csv
write_spectral_factor_csv(
longitude=None,
latitude=None,
timestamps=timestamps,
spectral_factor_dictionary=spectral_factor_series.components,
filename=csv,
index=index,
)
if statistics:
from pvgisprototype.api.series.statistics import (
print_spectral_factor_statistics,
)
print_spectral_factor_statistics(
spectral_factor=spectral_factor_series.components,
spectral_factor_model=spectral_factor_model,
photovoltaic_module_type=photovoltaic_module_type,
timestamps=timestamps,
# groupby=groupby,
title="Spectral Factor Statistics",
rounding_places=rounding_places,
verbose=verbose,
show_footer=show_footer,
)
# if analysis:
# from pvgisprototype.cli.print import print_change_percentages_panel
# print_change_percentages_panel(
# longitude=longitude,
# latitude=latitude,
# elevation=elevation,
# timestamps=timestamps,
# dictionary=photovoltaic_power_output_series.components,
# # title=photovoltaic_power_output_series['Title'] + f" series {POWER_UNIT}",
# rounding_places=1, # minimalism
# index=index,
# surface_orientation=True,
# surface_tilt=True,
# fingerprint=fingerprint,
# verbose=verbose,
# )
if uniplot:
from pvgisprototype.api.plot import uniplot_spectral_factor_series
uniplot_spectral_factor_series(
spectral_factor_dictionary=spectral_factor_series.value,
spectral_factor_model=spectral_factor_model,
photovoltaic_module_type=photovoltaic_module_type,
timestamps=timestamps,
resample_large_series=resample_large_series,
# lines=True,
# supertitle="Spectral Mismatch Factor",
supertitle=spectral_factor_series.supertitle,
# title="Spectral Factor",
title=spectral_factor_series.title,
# label=photovoltaic_module_types,
# label=spectral_factor_series.label,
# extra_legend_labels=None,
# unit='',
terminal_width_fraction=terminal_width_fraction,
)
if metadata:
import click
from pvgisprototype.cli.print import print_command_metadata
print_command_metadata(context=click.get_current_context())
if fingerprint and not analysis:
from pvgisprototype.cli.print.fingerprint import print_finger_hash
print_finger_hash(dictionary=spectral_factor_series.presentation)
plot ¶
Modules:
| Name | Description |
|---|---|
horizon | |
uniplot | This module contains code adapted from spiel by Josh Karpel |
horizon ¶
Functions:
| Name | Description |
|---|---|
plot_horizon_profile_x | Plot a horizon height profile dynamically in polar coordinates along with |
plot_horizon_profile_x ¶
plot_horizon_profile_x(
solar_position_series: dict,
horizon_profile: NDArray,
labels: List[str],
colors=["cyan", "blue", "yellow"],
)
Plot a horizon height profile dynamically in polar coordinates along with positions of the sun in the sky (solar altitude).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
solar_position_series | dict | Dictionary containing solar position data. | required |
horizon_profile | NDArray | Array of horizon height values corresponding to azimuth angles. | required |
labels | List[str] | List of labels for the plot legend. | required |
colors | List[str] | List of colors for plotting different series (default is ["cyan", "blue", "yellow"]). | ['cyan', 'blue', 'yellow'] |
Notes
This function creates a polar plot showing the horizon height profile around a given geographic location and positions of the sun in the sky (solar altitude). The horizon height profile is represented as a radial plot, where the radial distance starting from the outer perimeter (horizontal plane circle) corresponds to the horizon height, and the clock-wise angle starting from the top center of the polar plot (0 degrees = North) represents the azimuthal direction.
Examples:
To use this function via the command line:
>>> pvgis-prototype position overview 7.9672 45.9684 --start-time "2010-06-15 05:00:00" --end-time "2010-06-15 20:00:00" --horizon-profile horizon_12_076.zarr --horizon-plot
This command will generate a polar plot, as shown below, showing the horizon profile and solar positions for the requested location and period.
┌─────────────────────────────────────────────┐ │ ⣀⠤⠤⠒⠒⢀⡠⣀⣀⡀⠉⠉⠉⠉⠒⠒⠤⠤⣀ │ │ ⣀⠤⠒⠉ ⣀⡠⠔⠒⠁ ⠠⣀⡀ ⠉⠒⠤⣀ │ │ ⢀⠤⠊ ⢀⠖⠉ ⠈⠑⠢⢄⡀ ⠑⠤⡀ │ │ ⢀⠔⠁ ⢀⡠⠒⠁ ⠈⠓⠤⡀ ⠈⠢⡀ │ │ ⢀⠁ ⢀⡐⠉ ⠈⠢⣀ ⠈⢆ │ │ ⡜ ⢀⡜ ⠑⣄ ⢣ │ │ ⡜ ⡀⢸ ⢢ ⢀ ⢣ │ │⢰⠁ ⡜ ⢇ ⠈⡆│ │⡇ ⠐ ⠈⡆⠄ ⢸│ │⡇ ⢱ ⢀ ⢀ ⡇ ⢸│ │⡇ ⢣ ⢀ ⡀ ⢠⠃ ⢸│ │⡇ ⢣ ⠠ ⠄ ⡎ ⢸│ │⢸⡀ ⢸ ⠁ ⠐ ⠄ ⠐ ⢰⠁ ⢀⡇│ │ ⢣ ⢣ ⡔⠁ ⡜ │ │ ⢣ ⠡⡄ ⡸ ⡜ │ │ ⠱⡀ ⠈⢆ ⠠⠊ ⢀⠎ │ │ ⠈⠢⡀ ⠑⠢⢄⡀ ⢀⡠⠔⠊⠁ ⢀⠔⠁ │ │ ⠈⠢⢄ ⠈⠑⠢⢄⣀⣀⣀⣀⣀⠤⠒⠉⠉⠁ ⡠⠔⠁ │ │ ⠉⠢⢄⡀ ⢀⡠⠔⠉ │ │ ⠈⠉⠒⠢⠤⢄⣀⣀⣀⣀⣀⣀⣀⣀⣀⡠⠤⠔⠒⠉⠁ │ └─────────────────────────────────────────────┘
Acknowledgment
Some assistance by : - Olav Stetter - Chrysa Stathaki
Source code in pvgisprototype/cli/plot/horizon.py
def plot_horizon_profile_x(
solar_position_series: dict,
horizon_profile: NDArray,
labels: List[str],
colors=["cyan", "blue", "yellow"],
):
"""
Plot a horizon height profile dynamically in polar coordinates along with
positions of the sun in the sky (solar altitude).
Parameters
----------
solar_position_series : dict
Dictionary containing solar position data.
horizon_profile : NDArray
Array of horizon height values corresponding to azimuth angles.
labels : List[str]
List of labels for the plot legend.
colors : List[str], optional
List of colors for plotting different series (default is ["cyan", "blue", "yellow"]).
Notes
-----
This function creates a polar plot showing the horizon height profile
around a given geographic location and positions of the sun in the sky
(solar altitude). The horizon height profile is represented as a radial
plot, where the radial distance starting from the outer perimeter
(horizontal plane circle) corresponds to the horizon height, and the
clock-wise angle starting from the top center of the polar plot (0 degrees
= North) represents the azimuthal direction.
Examples
--------
To use this function via the command line:
>>> pvgis-prototype position overview 7.9672 45.9684 --start-time "2010-06-15 05:00:00" --end-time "2010-06-15 20:00:00" --horizon-profile horizon_12_076.zarr --horizon-plot
This command will generate a polar plot, as shown below, showing the
horizon profile and solar positions for the requested location and period.
┌─────────────────────────────────────────────┐
│ ⣀⠤⠤⠒⠒⢀⡠⣀⣀⡀⠉⠉⠉⠉⠒⠒⠤⠤⣀ │
│ ⣀⠤⠒⠉ ⣀⡠⠔⠒⠁ ⠠⣀⡀ ⠉⠒⠤⣀ │
│ ⢀⠤⠊ ⢀⠖⠉ ⠈⠑⠢⢄⡀ ⠑⠤⡀ │
│ ⢀⠔⠁ ⢀⡠⠒⠁ ⠈⠓⠤⡀ ⠈⠢⡀ │
│ ⢀⠁ ⢀⡐⠉ ⠈⠢⣀ ⠈⢆ │
│ ⡜ ⢀⡜ ⠑⣄ ⢣ │
│ ⡜ ⡀⢸ ⢢ ⢀ ⢣ │
│⢰⠁ ⡜ ⢇ ⠈⡆│
│⡇ ⠐ ⠈⡆⠄ ⢸│
│⡇ ⢱ ⢀ ⢀ ⡇ ⢸│
│⡇ ⢣ ⢀ ⡀ ⢠⠃ ⢸│
│⡇ ⢣ ⠠ ⠄ ⡎ ⢸│
│⢸⡀ ⢸ ⠁ ⠐ ⠄ ⠐ ⢰⠁ ⢀⡇│
│ ⢣ ⢣ ⡔⠁ ⡜ │
│ ⢣ ⠡⡄ ⡸ ⡜ │
│ ⠱⡀ ⠈⢆ ⠠⠊ ⢀⠎ │
│ ⠈⠢⡀ ⠑⠢⢄⡀ ⢀⡠⠔⠊⠁ ⢀⠔⠁ │
│ ⠈⠢⢄ ⠈⠑⠢⢄⣀⣀⣀⣀⣀⠤⠒⠉⠉⠁ ⡠⠔⠁ │
│ ⠉⠢⢄⡀ ⢀⡠⠔⠉ │
│ ⠈⠉⠒⠢⠤⢄⣀⣀⣀⣀⣀⣀⣀⣀⣀⡠⠤⠔⠒⠉⠁ │
└─────────────────────────────────────────────┘
Acknowledgment
--------------
Some assistance by :
- Olav Stetter
- Chrysa Stathaki
"""
from pvgisprototype.cli.print.getters import get_value_or_default
from pvgisprototype.api.position.models import (
SolarPositionParameter,
)
# Generate equidistant azimuthal directions
azimuthal_directions_radians = np.linspace(0, 2 * np.pi, horizon_profile.size)
plot(
xs=np.degrees(azimuthal_directions_radians),
ys=horizon_profile,
lines=True,
width=45,
height=3,
x_gridlines=[],
y_gridlines=[],
color=[colors[1]],
legend_labels=[labels[1]],
character_set="braille",
)
# Calculate polar coordinates (x, y) for the horitontal plane and horizon height profile
horizon_profile_radians = np.radians(horizon_profile) # input in degrees
x_horizontal_plane = np.sin(azimuthal_directions_radians) * np.pi / 2
y_horizontal_plane = np.cos(azimuthal_directions_radians) * np.pi / 2
x_horizon = x_horizontal_plane - np.sin(azimuthal_directions_radians) * horizon_profile_radians
y_horizon = y_horizontal_plane - np.cos(azimuthal_directions_radians) * horizon_profile_radians
# Loop over possibly multiple solar positioning algorithms ?
for model_name, model_result in solar_position_series.items():
# Get altitude
solar_altitude_in_radians = get_value_or_default(
model_result,
SolarPositionParameterColumnName.altitude
)
# Mask values outside the azimuth circle. Attention : overwrite original variables !
solar_altitude_in_radians = where(
solar_altitude_in_radians >= 0,
solar_altitude_in_radians,
nan
)
# Get azimuth
solar_azimuth_radians = get_value_or_default(
model_result,
SolarPositionParameterColumnName.azimuth
)
# Convert solar azimuth in degrees series to radians
# Calculate polar coordinates (x, y) for solar azimuth and altitude
x_solar_azimuth = np.sin(solar_azimuth_radians) * np.pi / 2
y_solar_azimuth = np.cos(solar_azimuth_radians) * np.pi / 2
x_polar_solar_altitude = x_solar_azimuth - np.sin(solar_azimuth_radians) * solar_altitude_in_radians
y_polar_solar_altitude = y_solar_azimuth - np.cos(solar_azimuth_radians) * solar_altitude_in_radians
x_series = [
x_horizontal_plane,
x_horizon,
x_polar_solar_altitude,
]
y_series = [
y_horizontal_plane,
y_horizon,
y_polar_solar_altitude,
]
# Plot the horizon profile polar coordinates in Cartesian space
plot(
xs=x_series,
ys=y_series,
lines=[True, True, False],
width=45,
height=20,
x_gridlines=[],
y_gridlines=[],
color=colors,
legend_labels=labels,
character_set="braille",
)
uniplot ¶
This module contains code adapted from spiel by Josh Karpel Original source: https://github.com/JoshKarpel/spiel/blob/bd64b7f2f04bdbe15c081360990ec6d4c93f0141/spiel/plot.py License: MIT (https://github.com/JoshKarpel/spiel/blob/main/LICENSE)
position ¶
Modules:
| Name | Description |
|---|---|
altitude | CLI module to calculate the solar altitude angle parameters over a |
azimuth | |
declination | CLI module to calculate the solar declination angle for a location and moment in time. |
event_time | |
hour_angle | |
incidence | CLI module to calculate the solar incidence angle for a solar surface at a |
introduction | |
overview | CLI module to calculate and overview the solar position parameters over a |
position | Important sun and solar surface position parameters in calculating the amount of solar radiation that reaches a particular location on the Earth's surface |
shading | CLI module to calculate and overview the solar position parameters over a |
zenith | CLI module to calculate the solar zenith angle for a location and a single moment in time. |
altitude ¶
CLI module to calculate the solar altitude angle parameters over a location and a moment in time.
Functions:
| Name | Description |
|---|---|
altitude | Calculate the solar altitude angle above the horizon. |
altitude ¶
altitude(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
model: Annotated[
list[SolarPositionModel],
typer_option_solar_position_model,
] = [noaa],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = milne,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool,
"Visually cluster time series results per model",
] = False,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
panels: Annotated[
bool, typer_option_panels_output
] = False,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
) -> SolarAltitude
Calculate the solar altitude angle above the horizon.
The solar altitude angle (SAA) is the complement of the solar zenith angle, measuring from the horizon directly below the sun to the sun itself. An altitude of 0 degrees means the sun is on the horizon, and an altitude of 90 degrees means the sun is directly overhead.
Source code in pvgisprototype/cli/position/altitude.py
@log_function_call
def altitude(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
model: Annotated[list[SolarPositionModel], typer_option_solar_position_model] = [
SolarPositionModel.noaa
],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SolarTimeModel.milne,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool, "Visually cluster time series results per model"
] = False,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
panels: Annotated[bool, typer_option_panels_output] = False,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
) -> SolarAltitude:
"""Calculate the solar altitude angle above the horizon.
The solar altitude angle (SAA) is the complement of the solar zenith angle,
measuring from the horizon directly below the sun to the sun itself. An
altitude of 0 degrees means the sun is on the horizon, and an altitude of
90 degrees means the sun is directly overhead.
Parameters
----------
"""
utc_timestamps = convert_timestamps_to_utc(
user_requested_timezone=timezone,
user_requested_timestamps=timestamps,
)
# Why does the callback function `_parse_model` not work?
solar_position_models = select_models(
SolarPositionModel, model
) # Using a callback fails!
solar_altitude_series = calculate_solar_altitude_series(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
solar_position_models=solar_position_models,
# solar_time_model=solar_time_model,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
angle_output_units=angle_output_units,
array_backend=array_backend,
dtype=dtype,
verbose=verbose,
validate_output=validate_output,
fingerprint=fingerprint,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if not quiet:
from pvgisprototype.cli.print.position.data import print_solar_position_series_table
print_solar_position_series_table(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_altitude_series,
position_parameters=[SolarPositionParameter.altitude],
title="Solar Altitude Series",
index=index,
surface_orientation=None,
surface_tilt=None,
incidence=None,
user_requested_timestamps=timestamps,
user_requested_timezone=timezone,
rounding_places=rounding_places,
group_models=group_models,
panels=panels,
)
if csv:
from pvgisprototype.cli.write import write_solar_position_series_csv
write_solar_position_series_csv(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_altitude_series,
position_parameters=solar_position_parameters,
# user_requested_timestamps=timestamps,
# user_requested_timezone=timezone,
index=index,
rounding_places=rounding_places,
# group_models=group_models,
filename=csv,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_solar_position_series
uniplot_solar_position_series(
solar_position_series=solar_altitude_series,
position_parameters=[SolarPositionParameter.altitude],
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
longitude=longitude,
latitude=latitude,
resample_large_series=resample_large_series,
lines=True,
supertitle="Solar Altitude Series",
title="Solar Altitude",
label="Altitude",
legend_labels=None,
terminal_width_fraction=terminal_width_fraction,
verbose=verbose,
)
azimuth ¶
Functions:
| Name | Description |
|---|---|
azimuth | Calculate the solar azimuth angle. |
azimuth ¶
azimuth(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
model: Annotated[
List[SolarPositionModel],
typer_option_solar_position_model,
] = [noaa],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = milne,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool,
"Visually cluster time series results per model",
] = False,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
panels: Annotated[
bool, typer_option_panels_output
] = False,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
)
Calculate the solar azimuth angle.
The solar azimuth angle (Az) specifies the east-west orientation of the sun. It is usually measured from the south, going positive to the west. The exact definitions can vary, with some sources defining the azimuth with respect to the north, so care must be taken to use the appropriate convention.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Returns | | required | |
solar_azimuth | | required |
Source code in pvgisprototype/cli/position/azimuth.py
@log_function_call
def azimuth(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
model: Annotated[List[SolarPositionModel], typer_option_solar_position_model] = [
SolarPositionModel.noaa
],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SolarTimeModel.milne,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool, "Visually cluster time series results per model"
] = False,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
panels: Annotated[bool, typer_option_panels_output] = False,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
):
"""Calculate the solar azimuth angle.
The solar azimuth angle (Az) specifies the east-west orientation of the
sun. It is usually measured from the south, going positive to the west. The
exact definitions can vary, with some sources defining the azimuth with
respect to the north, so care must be taken to use the appropriate
convention.
Parameters
----------
Returns
-------
solar_azimuth: float
"""
utc_timestamps = convert_timestamps_to_utc(
user_requested_timezone=timezone,
user_requested_timestamps=timestamps,
)
# Why does the callback function `_parse_model` not work?
solar_position_models = select_models(
SolarPositionModel, model
) # Using a callback fails!
solar_azimuth_series = calculate_solar_azimuth_series(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
solar_position_models=solar_position_models,
solar_time_model=solar_time_model,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
angle_output_units=angle_output_units,
verbose=verbose,
validate_output=validate_output,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if not quiet:
from pvgisprototype.cli.print.position.data import print_solar_position_series_table
print_solar_position_series_table(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_azimuth_series,
position_parameters=[SolarPositionParameter.azimuth],
title="Solar Azimuth Series",
index=index,
surface_orientation=None,
surface_tilt=None,
incidence=None,
user_requested_timestamps=timestamps,
user_requested_timezone=timezone,
rounding_places=rounding_places,
group_models=group_models,
panels=panels,
)
if csv:
from pvgisprototype.cli.write import write_solar_position_series_csv
write_solar_position_series_csv(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_azimuth_series,
position_parameters=[SolarPositionParameter.azimuth],
# user_requested_timestamps=timestamps,
# user_requested_timezone=timezone,
index=index,
rounding_places=rounding_places,
# group_models=group_models,
filename=csv,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_solar_position_series
uniplot_solar_position_series(
solar_position_series=solar_azimuth_series,
position_parameters=[SolarPositionParameter.azimuth],
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
resample_large_series=resample_large_series,
lines=True,
supertitle="Solar Azimuth Series",
title="Solar Azimuth",
label="Azimuth",
legend_labels=None,
terminal_width_fraction=terminal_width_fraction,
verbose=verbose,
)
declination ¶
CLI module to calculate the solar declination angle for a location and moment in time.
Functions:
| Name | Description |
|---|---|
declination | Calculate the solar declination angle |
declination ¶
declination(
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = TIMEZONE_DEFAULT,
local_time: Annotated[
bool, typer_option_local_time
] = False,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
solar_declination_model: Annotated[
List[SolarDeclinationModel],
typer_option_solar_declination_model,
] = [pvis],
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool,
"Visually cluster time series results per model",
] = False,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
panels: Annotated[
bool, typer_option_panels_output
] = False,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
) -> None
Calculate the solar declination angle
The solar declination (delta) is the angle between the line from the Earth to the Sun and the plane of the Earth's equator. It varies between ±23.45 degrees over the course of a year as the Earth orbits the Sun.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Returns | | required | |
solar_declination | | required |
Source code in pvgisprototype/cli/position/declination.py
@log_function_call
def declination(
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = TIMEZONE_DEFAULT,
local_time: Annotated[bool, typer_option_local_time] = False,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
solar_declination_model: Annotated[
List[SolarDeclinationModel], typer_option_solar_declination_model
] = [SolarDeclinationModel.pvis],
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
angle_output_units: Annotated[str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool, "Visually cluster time series results per model"
] = False,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
panels: Annotated[bool, typer_option_panels_output] = False,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
) -> None:
"""Calculate the solar declination angle
The solar declination (delta) is the angle between the line from the Earth
to the Sun and the plane of the Earth's equator. It varies between ±23.45
degrees over the course of a year as the Earth orbits the Sun.
Parameters
----------
Returns
-------
solar_declination: float
"""
utc_timestamps = convert_timestamps_to_utc(
user_requested_timezone=timezone,
user_requested_timestamps=timestamps,
)
# Why does the callback function `_parse_model` not work?
solar_declination_models = select_models(
SolarDeclinationModel, solar_declination_model
) # Using a callback fails!
solar_declination_series = calculate_solar_declination_series(
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
solar_declination_models=solar_declination_models,
eccentricity_amplitude=eccentricity_amplitude,
eccentricity_phase_offset=eccentricity_phase_offset,
angle_output_units=angle_output_units,
array_backend=array_backend,
dtype=dtype,
verbose=verbose,
validate_output=validate_output,
)
if not quiet:
from pvgisprototype.cli.print.position.data import print_solar_position_series_table
print_solar_position_series_table(
longitude=None,
latitude=None,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_declination_series,
title="Solar Declination Angle",
index=index,
surface_orientation=None,
surface_tilt=None,
incidence=None,
user_requested_timestamps=timestamps,
user_requested_timezone=timezone,
rounding_places=rounding_places,
position_parameters=[SolarPositionParameter.declination],
group_models=group_models,
panels=panels,
)
if csv:
from pvgisprototype.cli.write import write_solar_position_series_csv
write_solar_position_series_csv(
longitude=None,
latitude=None,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_declination_series,
position_parameters=solar_position_parameters,
# user_requested_timestamps=timestamps,
# user_requested_timezone=timezone,
index=index,
rounding_places=rounding_places,
# group_models=group_models,
filename=csv,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_solar_position_series
uniplot_solar_position_series(
solar_position_series=solar_declination_series,
position_parameters=[SolarPositionParameter.declination],
timestamps=utc_timestamps,
# surface_orientation=True,
# surface_tilt=True,
resample_large_series=resample_large_series,
lines=True,
supertitle="Solar Declination Series",
title="Solar Declination",
label="Declination",
legend_labels=None,
terminal_width_fraction=terminal_width_fraction,
verbose=verbose,
)
event_time ¶
Functions:
| Name | Description |
|---|---|
event_time | |
event_time ¶
event_time(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = TIMEZONE_DEFAULT,
event: Annotated[
List[SolarEvent], typer_option_solar_event
] = [None],
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
solar_position_model: Annotated[
List[SolarPositionModel],
typer_option_solar_position_model,
] = [noaa],
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = milne,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool,
"Visually cluster time series results per model",
] = False,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
panels: Annotated[
bool, typer_option_panels_output
] = False,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
)
Source code in pvgisprototype/cli/position/event_time.py
@log_function_call
def event_time(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = TIMEZONE_DEFAULT,
event: Annotated[List[SolarEvent], typer_option_solar_event] = [None],
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
solar_position_model: Annotated[
List[SolarPositionModel], typer_option_solar_position_model
] = [SolarPositionModel.noaa],
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SolarTimeModel.milne,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool, "Visually cluster time series results per model"
] = False,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
panels: Annotated[bool, typer_option_panels_output] = False,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
):
"""
"""
utc_timestamps = convert_timestamps_to_utc(
user_requested_timezone=timezone,
user_requested_timestamps=timestamps,
)
solar_event_time_series = calculate_event_time_series(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
event=event,
dtype=dtype,
array_backend=array_backend,
# validate_output=validate_output,
verbose=verbose,
log=log,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
if not quiet:
from pvgisprototype.cli.print.position.data import print_solar_position_series_table
print_solar_position_series_table(
longitude=longitude,
latitude=None,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_event_time_series,
position_parameters=[SolarPositionParameter.event_type, SolarPositionParameter.event_time],
title="Solar Event Time",
index=index,
surface_orientation=True,
surface_tilt=True,
incidence=True,
user_requested_timestamps=timestamps,
user_requested_timezone=timezone,
rounding_places=rounding_places,
group_models=group_models,
panels=panels,
)
if csv:
from pvgisprototype.cli.write import write_solar_position_series_csv
write_solar_position_series_csv(
longitude=longitude,
latitude=None,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_event_time_series,
position_parameters=solar_position_parameters,
# user_requested_timestamps=timestamps,
# user_requested_timezone=timezone,
index=index,
rounding_places=rounding_places,
# group_models=group_models,
filename=csv,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_solar_position_series
uniplot_solar_position_series(
solar_position_series=solar_hour_angle_series,
position_parameters=[SolarPositionParameter.hour_angle],
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
surface_orientation=True,
surface_tilt=True,
resample_large_series=resample_large_series,
lines=True,
supertitle="Solar Hour Angle Series",
title="Solar Hour Angle",
label="Hour Angle",
legend_labels=None,
terminal_width_fraction=terminal_width_fraction,
verbose=verbose,
)
hour_angle ¶
Functions:
| Name | Description |
|---|---|
hour_angle | Calculate the hour angle 'ω = (ST / 3600 - 12) * 15 * π / 180' |
hour_angle ¶
hour_angle(
longitude: Annotated[float, typer_argument_longitude],
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
str | None, typer_option_timezone
] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
solar_position_model: Annotated[
List[SolarPositionModel],
typer_option_solar_position_model,
] = [noaa],
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = milne,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool,
"Visually cluster time series results per model",
] = False,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
panels: Annotated[
bool, typer_option_panels_output
] = False,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
)
Calculate the hour angle 'ω = (ST / 3600 - 12) * 15 * π / 180'
The hour angle (ω) is the angle at any instant through which the earth has to turn to bring the meridian of the observer directly in line with the sun's rays measured in radian. In other words, it is a measure of time, expressed in angular measurement, usually degrees, from solar noon. It increases by 15° per hour, negative before solar noon and positive after solar noon.
Source code in pvgisprototype/cli/position/hour_angle.py
@log_function_call
def hour_angle(
longitude: Annotated[float, typer_argument_longitude],
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[str | None, typer_option_timezone] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
solar_position_model: Annotated[
List[SolarPositionModel], typer_option_solar_position_model
] = [SolarPositionModel.noaa],
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SolarTimeModel.milne,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool, "Visually cluster time series results per model"
] = False,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
panels: Annotated[bool, typer_option_panels_output] = False,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
):
"""Calculate the hour angle 'ω = (ST / 3600 - 12) * 15 * π / 180'
The hour angle (ω) is the angle at any instant through which the earth has
to turn to bring the meridian of the observer directly in line with the
sun's rays measured in radian. In other words, it is a measure of time,
expressed in angular measurement, usually degrees, from solar noon. It
increases by 15° per hour, negative before solar noon and positive after
solar noon.
"""
utc_timestamps = convert_timestamps_to_utc(
user_requested_timezone=timezone,
user_requested_timestamps=timestamps,
)
# Why does the callback function `_parse_model` not work?
solar_position_models = select_models(
SolarPositionModel, solar_position_model
) # Using a callback fails!
solar_hour_angle_series = calculate_solar_hour_angle_series(
longitude=longitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
solar_position_models=solar_position_models,
solar_time_model=solar_time_model,
angle_output_units=angle_output_units,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
if not quiet:
from pvgisprototype.cli.print.position.data import print_solar_position_series_table
print_solar_position_series_table(
longitude=longitude,
latitude=None,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_hour_angle_series,
position_parameters=[SolarPositionParameter.hour_angle],
title="Solar Position Overview",
index=index,
surface_orientation=True,
surface_tilt=True,
incidence=True,
user_requested_timestamps=timestamps,
user_requested_timezone=timezone,
rounding_places=rounding_places,
group_models=group_models,
panels=panels,
)
if csv:
from pvgisprototype.cli.write import write_solar_position_series_csv
write_solar_position_series_csv(
longitude=longitude,
latitude=None,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_hour_angle_series,
position_parameters=solar_position_parameters,
# user_requested_timestamps=timestamps,
# user_requested_timezone=timezone,
index=index,
rounding_places=rounding_places,
# group_models=group_models,
filename=csv,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_solar_position_series
uniplot_solar_position_series(
solar_position_series=solar_hour_angle_series,
position_parameters=[SolarPositionParameter.hour_angle],
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
surface_orientation=True,
surface_tilt=True,
resample_large_series=resample_large_series,
lines=True,
supertitle="Solar Hour Angle Series",
title="Solar Hour Angle",
label="Hour Angle",
legend_labels=None,
terminal_width_fraction=terminal_width_fraction,
verbose=verbose,
)
incidence ¶
CLI module to calculate the solar incidence angle for a solar surface at a location, orientation, tilt and moment in time.
Functions:
| Name | Description |
|---|---|
incidence | Calculate the angle of solar incidence |
incidence ¶
incidence(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
random_surface_orientation: Annotated[
bool, typer_option_random_surface_orientation
] = False,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
random_surface_tilt: Annotated[
bool, typer_option_random_surface_tilt
] = False,
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
solar_incidence_model: Annotated[
List[SolarIncidenceModel],
typer_option_solar_incidence_model,
] = [iqbal],
horizon_profile: Annotated[
DataArray | None, typer_option_horizon_profile
] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model
] = pvgis,
complementary_incidence_angle: Annotated[
bool,
typer_option_sun_to_surface_plane_incidence_angle,
] = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = milne,
zero_negative_solar_incidence_angle: Annotated[
bool,
typer_option_zero_negative_solar_incidence_angle,
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool,
"Visually cluster time series results per model",
] = False,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
panels: Annotated[
bool, typer_option_panels_output
] = False,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
)
Calculate the angle of solar incidence
The angle of incidence (also known as theta) is the angle between the direct beam of sunlight and the line perpendicular (normal) to the surface. If the sun is directly overhead and the surface is flat (horizontal), the angle of incidence is 0°.
Source code in pvgisprototype/cli/position/incidence.py
@log_function_call
def incidence(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
random_surface_orientation: Annotated[
bool, typer_option_random_surface_orientation
] = False,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
random_surface_tilt: Annotated[
bool, typer_option_random_surface_tilt
] = False,
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
solar_incidence_model: Annotated[
List[SolarIncidenceModel], typer_option_solar_incidence_model
] = [SolarIncidenceModel.iqbal],
horizon_profile: Annotated[DataArray | None, typer_option_horizon_profile] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model] = ShadingModel.pvgis, # for 'overview' : should be one !
complementary_incidence_angle: Annotated[
bool, typer_option_sun_to_surface_plane_incidence_angle
] = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SolarTimeModel.milne,
zero_negative_solar_incidence_angle: Annotated[
bool, typer_option_zero_negative_solar_incidence_angle
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool, "Visually cluster time series results per model"
] = False,
# statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
panels: Annotated[bool, typer_option_panels_output] = False,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
):
"""Calculate the angle of solar incidence
The angle of incidence (also known as theta) is the angle between the
direct beam of sunlight and the line perpendicular (normal) to the surface.
If the sun is directly overhead and the surface is flat (horizontal), the
angle of incidence is 0°.
"""
utc_timestamps = convert_timestamps_to_utc(
user_requested_timezone=timezone,
user_requested_timestamps=timestamps,
)
# --------------------------------------------------------------- Idea ---
# if not given, optimise tilt and orientation... ?
# ------------------------------------------------------------------------
if random_surface_orientation:
import random
surface_tilt = random.vonmisesvariate(pi, kappa=0) # radians
if random_surface_tilt:
import random
surface_tilt = random.uniform(0, pi / 2) # radians
solar_incidence_models = select_models(
SolarIncidenceModel, solar_incidence_model
) # Using a callback fails!
solar_incidence_series = calculate_solar_incidence_series(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
surface_orientation=SurfaceOrientation(
value=surface_orientation, unit=RADIANS
), # Typer does not easily support custom types !
surface_tilt=SurfaceTilt(
value=surface_tilt, unit=RADIANS
), # Typer does not easily support custom types !
solar_incidence_models=solar_incidence_models,
horizon_profile=horizon_profile,
shading_model=shading_model,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
# solar_time_model=solar_time_model,
eccentricity_amplitude=eccentricity_amplitude,
eccentricity_phase_offset=eccentricity_phase_offset,
angle_output_units=angle_output_units,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
validate_output=validate_output,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if not quiet:
from pvgisprototype.cli.print.position.data import print_solar_position_series_table
print_solar_position_series_table(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
table=solar_incidence_series,
position_parameters=[SolarPositionParameter.incidence],
title="Solar Incidence",
index=index,
surface_orientation=None,
surface_tilt=None,
incidence=True,
user_requested_timestamps=timestamps,
user_requested_timezone=timezone,
rounding_places=rounding_places,
group_models=group_models,
panels=panels,
)
# from pvgisprototype.cli.print import print_solar_incidence_series_in_columns
# print_solar_incidence_series_in_columns(
# longitude=longitude,
# latitude=latitude,
# timestamps=timestamps,
# timezone=timezone,
# table=solar_incidence_series,
# )
if csv:
from pvgisprototype.cli.write import write_solar_position_series_csv
write_solar_position_series_csv(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_incidence_series,
position_parameters=solar_position_parameters,
# user_requested_timestamps=timestamps,
# user_requested_timezone=timezone,
index=index,
rounding_places=rounding_places,
# group_models=group_models,
filename=csv,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_solar_position_series
uniplot_solar_position_series(
solar_position_series=solar_incidence_series,
position_parameters=[SolarPositionParameter.incidence],
timestamps=timestamps, # User requested or UTC ?
surface_orientation=None,
surface_tilt=None,
resample_large_series=resample_large_series,
lines=True,
supertitle="Solar Position Series",
title="Solar Position",
label="Incidence",
legend_labels=None,
terminal_width_fraction=terminal_width_fraction,
verbose=verbose,
)
introduction ¶
Functions:
| Name | Description |
|---|---|
introduction | A short introduction on solar position |
introduction ¶
A short introduction on solar position
Source code in pvgisprototype/cli/position/introduction.py
def introduction():
"""A short introduction on solar position"""
introduction = """
[underline]Solar position[/underline] consists of a series of angular
measurements between the position of the sun in the sky and a location on
the surface of the earth for a moment or a period in time.
"""
note = """Internally, [bold]timestamps[/bold] are converted to [magenta]UTC[/magenta] and [bold]angles[/bold] are measured in [magenta]radians[/magenta] !
"""
from rich.panel import Panel
note_in_a_panel = Panel(
"[italic]{}[/italic]".format(note),
title="[bold cyan]Note[/bold cyan]",
width=78,
)
from rich.console import Console
console = Console()
# introduction.wrap(console, 30)
console.print(introduction)
console.print(note_in_a_panel)
console.print(A_PRIMER_ON_SOLAR_GEOMETRY)
overview ¶
CLI module to calculate and overview the solar position parameters over a location for a period in time.
Functions:
| Name | Description |
|---|---|
overview | |
overview ¶
overview(
ctx: Context,
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
random_surface_orientation: Annotated[
bool, typer_option_random_surface_orientation
] = False,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
random_surface_tilt: Annotated[
bool, typer_option_random_surface_tilt
] = False,
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = TIMEZONE_DEFAULT,
event: Annotated[
List[SolarEvent] | None, typer_option_solar_event
] = [sunrise, sunset],
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
solar_position_model: Annotated[
List[SolarPositionModel],
typer_option_solar_position_model,
] = [noaa],
position_parameter: Annotated[
List[SolarPositionParameter],
typer_option_solar_position_parameter,
] = [all],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
sun_horizon_position: Annotated[
List[SunHorizonPositionModel],
typer_option_sun_horizon_position,
] = SUN_HORIZON_POSITION_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = milne,
complementary_incidence_angle: Annotated[
bool,
typer_option_sun_to_surface_plane_incidence_angle,
] = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: Annotated[
bool,
typer_option_zero_negative_solar_incidence_angle,
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
horizon_profile: Annotated[
DataArray | None, typer_option_horizon_profile
] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model
] = pvgis,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool,
"Visually cluster time series results per model",
] = False,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
horizon_plot: Annotated[
bool, typer_option_horizon_profile_plot
] = False,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
version: Annotated[
bool, typer_option_version
] = VERSION_FLAG_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
panels: Annotated[
bool, typer_option_panels_output
] = False,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
) -> None
Source code in pvgisprototype/cli/position/overview.py
@log_function_call
def overview(
ctx: typer.Context,
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
random_surface_orientation: Annotated[
bool, typer_option_random_surface_orientation
] = False,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
random_surface_tilt: Annotated[
bool, typer_option_random_surface_tilt
] = False,
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = TIMEZONE_DEFAULT,
event: Annotated[List[SolarEvent] | None, typer_option_solar_event] = [SolarEvent.sunrise, SolarEvent.sunset],
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
solar_position_model: Annotated[
List[SolarPositionModel], typer_option_solar_position_model
] = [SolarPositionModel.noaa],
position_parameter: Annotated[
List[SolarPositionParameter], typer_option_solar_position_parameter
] = [SolarPositionParameter.all],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
sun_horizon_position: Annotated[
List[SunHorizonPositionModel], typer_option_sun_horizon_position
] = SUN_HORIZON_POSITION_DEFAULT,
# refracted_solar_zenith: Annotated[
# float | None, typer_option_refracted_solar_zenith
# ] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SolarTimeModel.milne,
# solar_incidence_model: Annotated[SolarIncidenceModel, typer_option_solar_incidence_model] = SolarIncidenceModel.iqbal,
complementary_incidence_angle: Annotated[
bool, typer_option_sun_to_surface_plane_incidence_angle
] = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: Annotated[
bool, typer_option_zero_negative_solar_incidence_angle
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
horizon_profile: Annotated[DataArray | None, typer_option_horizon_profile] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model] = ShadingModel.pvgis, # for 'overview' : should be one !
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool, "Visually cluster time series results per model"
] = False,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
horizon_plot: Annotated[
bool, typer_option_horizon_profile_plot
] = False,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
version: Annotated[bool, typer_option_version] = VERSION_FLAG_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
panels: Annotated[bool, typer_option_panels_output] = False,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
) -> None:
""" """
utc_timestamps = convert_timestamps_to_utc(
user_requested_timezone=timezone,
user_requested_timestamps=timestamps,
)
# Why does the callback function `_parse_model` not work?
solar_position_models = select_models(
SolarPositionModel, solar_position_model
) # Using a callback fails!
# Why does the callback function `_parse_model` not work?
solar_position_series = calculate_solar_position_overview_series(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
event=event,
surface_orientation=surface_orientation,
surface_tilt=surface_tilt,
solar_position_models=solar_position_models,
sun_horizon_position=sun_horizon_position,
horizon_profile=horizon_profile,
shading_model=shading_model,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
# unrefracted_solar_zenith=unrefracted_solar_zenith,
solar_time_model=solar_time_model,
# solar_incidence_model=solar_incidence_model,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
# time_output_units=time_output_units,
angle_output_units=angle_output_units,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
validate_output=validate_output,
fingerprint=fingerprint,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
position_parameter = ctx.params.get(
"position_parameter"
) # Bug in Typer that is not passing correctly whatever is in the context ?
solar_position_parameters = select_models(
SolarPositionParameter, position_parameter
) # Using a callback fails!
if not quiet:
from pvgisprototype.cli.print.position.data import print_solar_position_series_table
print_solar_position_series_table(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_position_series,
position_parameters=solar_position_parameters,
title="Solar Position Overview",
index=index,
version=version,
fingerprint=fingerprint,
surface_orientation=True,
surface_tilt=True,
incidence=True,
user_requested_timestamps=timestamps,
user_requested_timezone=timezone,
rounding_places=rounding_places,
group_models=group_models,
panels=panels,
)
if csv:
from pvgisprototype.cli.write import write_solar_position_series_csv
write_solar_position_series_csv(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_position_series,
position_parameters=solar_position_parameters,
# user_requested_timestamps=timestamps,
# user_requested_timezone=timezone,
index=index,
rounding_places=rounding_places,
# group_models=group_models,
filename=csv,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_solar_position_series
# print(f'Input Solar position series : {solar_position_series}')
uniplot_solar_position_series(
solar_position_series=solar_position_series,
position_parameters=solar_position_parameters,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
longitude=longitude,
latitude=latitude,
surface_orientation=True,
surface_tilt=True,
resample_large_series=resample_large_series,
lines=True,
supertitle="Solar Position Series",
title="Solar Position",
label="Incidence", # Review Me !
legend_labels=None,
terminal_width_fraction=terminal_width_fraction,
verbose=verbose,
)
if horizon_plot:
from pvgisprototype.cli.plot.horizon import plot_horizon_profile_x
from numpy import degrees
# Check the unit of the horizon_profile
unit = horizon_profile.attrs.get('units', None) # Adjust the attribute name as necessary
# Convert to degrees if the unit is in radians
if unit == 'radians':
horizon_profile = degrees(horizon_profile)
else:
raise ValueError(f"Unknown unit for horizon_profile: {unit}")
plot_horizon_profile_x(
solar_position_series=solar_position_series,
horizon_profile=horizon_profile,
labels=["Horizontal plane", "Horizon height", "Solar altitude"],
# colors=["cyan", "magenta", "yellow"], # uncomment to override default
)
position ¶
Important sun and solar surface position parameters in calculating the amount of solar radiation that reaches a particular location on the Earth's surface
Functions:
| Name | Description |
|---|---|
main | Solar position algorithms |
main ¶
main(
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
debug: Annotated[
bool, Option(--debug, help="Enable debug mode")
] = False,
)
Solar position algorithms
Source code in pvgisprototype/cli/position/position.py
@app.callback()
def main(
# ctx: typer.Context,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
debug: Annotated[
bool, typer.Option("--debug", help="Enable debug mode")
] = False,
):
"""
Solar position algorithms
"""
# if verbose > 2:
# print(f"Executing command: {ctx.invoked_subcommand}")
if verbose > 0:
print("Will output verbosely")
# state["verbose"] = True
app.debug_mode = debug
shading ¶
CLI module to calculate and overview the solar position parameters over a location for a period in time.
Functions:
| Name | Description |
|---|---|
in_shade | |
in_shade ¶
in_shade(
ctx: Context,
horizon_profile: Annotated[
str | None, typer_argument_horizon_profile
],
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
random_surface_orientation: Annotated[
bool, typer_option_random_surface_orientation
] = False,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
random_surface_tilt: Annotated[
bool, typer_option_random_surface_tilt
] = False,
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = TIMEZONE_DEFAULT,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
shading_model: Annotated[
List[ShadingModel], typer_option_shading_model
] = [pvgis],
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
position_parameter: Annotated[
List[SolarPositionParameter],
typer_option_solar_position_parameter,
] = [all],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = milne,
solar_position_model: Annotated[
SolarPositionModel,
typer_option_solar_position_model,
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
complementary_incidence_angle: Annotated[
bool,
typer_option_sun_to_surface_plane_incidence_angle,
] = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: Annotated[
bool,
typer_option_zero_negative_solar_incidence_angle,
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool,
"Visually cluster time series results per model",
] = False,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
panels: Annotated[
bool, typer_option_panels_output
] = False,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
) -> None
Source code in pvgisprototype/cli/position/shading.py
@log_function_call
def in_shade(
ctx: typer.Context,
horizon_profile: Annotated[str | None, typer_argument_horizon_profile],
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
random_surface_orientation: Annotated[
bool, typer_option_random_surface_orientation
] = False,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
random_surface_tilt: Annotated[
bool, typer_option_random_surface_tilt
] = False,
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = TIMEZONE_DEFAULT,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
shading_model: Annotated[
List[ShadingModel], typer_option_shading_model] = [ShadingModel.pvgis],
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
position_parameter: Annotated[
List[SolarPositionParameter], typer_option_solar_position_parameter
] = [SolarPositionParameter.all],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SolarTimeModel.milne,
solar_position_model: Annotated[
SolarPositionModel, typer_option_solar_position_model
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
# solar_incidence_model: Annotated[SolarIncidenceModel, typer_option_solar_incidence_model] = SolarIncidenceModel.iqbal,
complementary_incidence_angle: Annotated[
bool, typer_option_sun_to_surface_plane_incidence_angle
] = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: Annotated[
bool, typer_option_zero_negative_solar_incidence_angle
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool, "Visually cluster time series results per model"
] = False,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
panels: Annotated[bool, typer_option_panels_output] = False,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
) -> None:
""" """
# pass
utc_timestamps = convert_timestamps_to_utc(
user_requested_timezone=timezone,
user_requested_timestamps=timestamps,
)
# Why does the callback function `_parse_model` not work?
shading_models = select_models(
ShadingModel, shading_model
) # Using a callback fails!
surface_in_shade_series = calculate_surface_in_shade_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
horizon_profile=horizon_profile,
shading_models=shading_models,
solar_time_model=solar_time_model,
solar_position_model=solar_position_model,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
# unrefracted_solar_zenith=unrefracted_solar_zenith,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
angle_output_units=angle_output_units,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
# position_parameter = ctx.params.get(
# "position_parameter"
# ) # Bug in Typer that is not passing correctly whatever is in the context ?
# solar_position_parameters = select_models(
# SolarPositionParameter, position_parameter
# ) # Using a callback fails!
if not quiet:
from pvgisprototype.cli.print.position.data import print_solar_position_series_table
print_solar_position_series_table(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=surface_in_shade_series,
position_parameters=[SolarPositionParameter.horizon, SolarPositionParameter.visible],
title="Surface in shade",
index=index,
surface_orientation=False,
surface_tilt=False,
incidence=False,
user_requested_timestamps=timestamps,
user_requested_timezone=timezone,
rounding_places=rounding_places,
group_models=group_models,
panels=panels,
)
# if csv:
# from pvgisprototype.cli.write import write_solar_position_series_csv
# write_solar_position_series_csv(
# longitude=longitude,
# latitude=latitude,
# timestamps=utc_timestamps,
# timezone=utc_timestamps.tz,
# table=shade_series,
# timing=True,
# declination=True,
# hour_angle=True,
# zenith=True,
# altitude=True,
# azimuth=True,
# surface_orientation=True,
# surface_tilt=True,
# incidence=True,
# user_requested_timestamps=timestamps,
# user_requested_timezone=timezone,
# # rounding_places=rounding_places,
# # group_models=group_models,
# filename=csv,
# )
if uniplot:
from pvgisprototype.api.plot import uniplot_solar_position_series
uniplot_solar_position_series(
solar_position_series=surface_in_shade_series,
position_parameters=[SolarPositionParameter.visible],
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
longitude=longitude,
latitude=latitude,
surface_orientation=True,
surface_tilt=True,
convert_false_to_none=False, # `False` plots histogram of "In-Shade"
resample_large_series=resample_large_series,
lines=True,
supertitle="Surface in Shade Series",
title="Surface in Shade",
label="Shade",
legend_labels=None,
terminal_width_fraction=terminal_width_fraction,
verbose=verbose,
)
zenith ¶
CLI module to calculate the solar zenith angle for a location and a single moment in time.
Functions:
| Name | Description |
|---|---|
zenith | Calculate the solar zenith angle |
zenith ¶
zenith(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
str | None, typer_option_timezone
] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
model: Annotated[
List[SolarPositionModel],
typer_option_solar_position_model,
] = [noaa],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool,
"Visually cluster time series results per model",
] = False,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
panels: Annotated[
bool, typer_option_panels_output
] = False,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
) -> None
Calculate the solar zenith angle
The solar zenith angle (SZA) is the angle between the zenith (directly overhead) and the line to the sun. A zenith angle of 0 degrees means the sun is directly overhead, while an angle of 90 degrees means the sun is on the horizon.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Returns | | required |
Source code in pvgisprototype/cli/position/zenith.py
@log_function_call
def zenith(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[str | None, typer_option_timezone] = TIMEZONE_DEFAULT,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
model: Annotated[List[SolarPositionModel], typer_option_solar_position_model] = [
SolarPositionModel.noaa
],
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
group_models: Annotated[
bool, "Visually cluster time series results per model"
] = False,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
panels: Annotated[bool, typer_option_panels_output] = False,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
) -> None:
"""Calculate the solar zenith angle
The solar zenith angle (SZA) is the angle between the zenith (directly
overhead) and the line to the sun. A zenith angle of 0 degrees means the
sun is directly overhead, while an angle of 90 degrees means the sun is on
the horizon.
Parameters
----------
Returns
-------
"""
utc_timestamps = convert_timestamps_to_utc(
user_requested_timezone=timezone,
user_requested_timestamps=timestamps,
)
# Why does the callback function `_parse_model` not work?
solar_position_models = select_models(
SolarPositionModel, model
) # Using a callback fails!
solar_zenith_series = calculate_solar_zenith_series(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
solar_position_models=solar_position_models,
# solar_time_model=solar_time_model,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
angle_output_units=angle_output_units,
array_backend=array_backend,
dtype=dtype,
verbose=verbose,
validate_output=validate_output,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if not quiet:
from pvgisprototype.cli.print.position.data import print_solar_position_series_table
print_solar_position_series_table(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_zenith_series,
position_parameters=[SolarPositionParameter.zenith],
title="Solar Zenith Series",
index=index,
surface_orientation=None,
surface_tilt=None,
incidence=None,
user_requested_timestamps=timestamps,
user_requested_timezone=timezone,
rounding_places=rounding_places,
group_models=group_models,
panels=panels,
)
if csv:
from pvgisprototype.cli.write import write_solar_position_series_csv
write_solar_position_series_csv(
longitude=longitude,
latitude=latitude,
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
table=solar_zenith_series,
position_parameters=solar_position_parameters,
# user_requested_timestamps=timestamps,
# user_requested_timezone=timezone,
index=index,
rounding_places=rounding_places,
# group_models=group_models,
filename=csv,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_solar_position_series
uniplot_solar_position_series(
solar_position_series=solar_zenith_series,
position_parameters=[SolarPositionParameter.zenith],
timestamps=utc_timestamps,
timezone=utc_timestamps.tz,
resample_large_series=resample_large_series,
lines=True,
supertitle="Solar Zenith Series",
title="Solar Zenith",
label="Zenith",
legend_labels=None,
terminal_width_fraction=terminal_width_fraction,
)
power ¶
Modules:
| Name | Description |
|---|---|
average_photon_energy | |
broadband | CLI module to calculate the photovoltaic power output over a |
broadband_multiple_surfaces | CLI module to calculate the photovoltaic power output over a |
broadband_rear_side | CLI module to calculate the photovoltaic power output over a |
energy_ | energy |
introduction | |
power | |
spectral | |
temperature | |
average_photon_energy ¶
Functions:
| Name | Description |
|---|---|
average_photon_energy | The Average Photon Energy (APE) characterises the energetic distribution |
average_photon_energy ¶
average_photon_energy(
global_irradiance_series_up_to_1050,
photon_flux_density,
electron_charge=ELECTRON_CHARGE,
)
The Average Photon Energy (APE) characterises the energetic distribution in an irradiance spectrum. It is calculated by dividing the irradiance [W/m² or eV/m²/sec] by the photon flux density [number of photons/m²/sec]. [1]_
References
.. [1] Jardine, C.N. & Gottschalg, Ralph & Betts, Thomas & Infield, David. (2002). Influence of Spectral Effects on the Performance of Multijunction Amorphous Silicon Cells. to be published.
Source code in pvgisprototype/cli/power/average_photon_energy.py
@log_function_call
def average_photon_energy( # series ?
global_irradiance_series_up_to_1050,
photon_flux_density, # number_of_photons_up_to_1050 ?
electron_charge=ELECTRON_CHARGE,
):
"""
The Average Photon Energy (APE) characterises the energetic distribution
in an irradiance spectrum. It is calculated by dividing the irradiance
[W/m² or eV/m²/sec] by the photon flux density [number of photons/m²/sec].
[1]_
References
----------
.. [1] Jardine, C.N. & Gottschalg, Ralph & Betts, Thomas & Infield, David.
(2002). Influence of Spectral Effects on the Performance of Multijunction
Amorphous Silicon Cells. to be published.
"""
average_photon_energy = calculate_average_photon_energy(
global_power_1050=global_irradiance_series_up_to_1050,
photon_flux_density=photon_flux_density,
electron_charge=ELECTRON_CHARGE,
)
print(average_photon_energy)
broadband ¶
CLI module to calculate the photovoltaic power output over a location for a period in time.
Functions:
| Name | Description |
|---|---|
photovoltaic_power_output_series | Estimate the photovoltaic power output for a location and a moment or period |
photovoltaic_power_output_series ¶
photovoltaic_power_output_series(
ctx: Context,
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[
DatetimeIndex | None, typer_argument_timestamps
] = str(now()),
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = None,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
photovoltaic_module_type: Annotated[
PhotovoltaicModuleType,
typer_option_photovoltaic_module_type,
] = Monofacial,
global_horizontal_irradiance: Annotated[
Path | None,
typer_option_global_horizontal_irradiance,
] = None,
direct_horizontal_irradiance: Annotated[
Path | None,
typer_option_direct_horizontal_irradiance,
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries,
typer_argument_spectral_factor_series,
] = SPECTRAL_FACTOR_DEFAULT,
temperature_series: Annotated[
TemperatureSeries, typer_option_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_option_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor,
typer_option_linke_turbidity_factor_series,
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
albedo: Annotated[
float | None, typer_option_albedo
] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel,
typer_option_solar_position_model,
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
sun_horizon_position: Annotated[
List[SunHorizonPositionModel],
typer_option_sun_horizon_position,
] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel,
typer_option_solar_incidence_model,
] = iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool,
typer_option_zero_negative_solar_incidence_angle,
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[
float, typer_option_solar_constant
] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
horizon_profile: Annotated[
DataArray | None, typer_option_horizon_profile
] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model
] = pvgis,
shading_states: Annotated[
List[ShadingState], typer_option_shading_state
] = [all],
photovoltaic_module: Annotated[
PhotovoltaicModuleModel,
typer_option_photovoltaic_module_model,
] = PHOTOVOLTAIC_MODULE_DEFAULT,
peak_power: Annotated[
float, typer_option_photovoltaic_module_peak_power
] = 1,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel,
typer_option_pv_power_algorithm,
] = king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm,
typer_option_module_temperature_algorithm,
] = faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = RADIANS,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
nomenclature: Annotated[
bool, typer_option_nomenclature
] = NOMENCLATURE_FLAG_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = METADATA_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = NoneValue,
profile: Annotated[
bool, typer_option_profiling
] = cPROFILE_FLAG_DEFAULT,
)
Estimate the photovoltaic power output for a location and a moment or period in time.
Estimate the photovoltaic power over a time series or an arbitrarily aggregated energy production of a PV system connected to the electricity grid (without battery storage) based on broadband solar irradiance, ambient temperature and wind speed.
Notes
The optional input parameters global_horizontal_irradiance and direct_horizontal_irradiance accept any Xarray-support data file format and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the functions that calculate the diffuse and direct components, are defined as global_horizontal_component and direct_horizontal_component. This is to avoid confusion at the function level. For example, the function calculate_diffuse_inclined_irradiance_series() can read the direct horizontal component (thus the name of it direct_horizontal_component as well as simulate it. The point is to make it clear that if the direct_horizontal_component parameter is True (which means the user has provided an external dataset), then read it using the select_time_series() function.
Source code in pvgisprototype/cli/power/broadband.py
@log_function_call
def photovoltaic_power_output_series(
ctx: typer.Context,
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[DatetimeIndex | None, typer_argument_timestamps] = str(
Timestamp.now()
),
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = None,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
photovoltaic_module_type: Annotated[
PhotovoltaicModuleType, typer_option_photovoltaic_module_type
] = PhotovoltaicModuleType.Monofacial,
global_horizontal_irradiance: Annotated[
Path | None, typer_option_global_horizontal_irradiance
] = None,
direct_horizontal_irradiance: Annotated[
Path | None, typer_option_direct_horizontal_irradiance
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries, typer_argument_spectral_factor_series
] = SPECTRAL_FACTOR_DEFAULT, # Accept also list of float values ?
temperature_series: Annotated[
TemperatureSeries, typer_option_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_option_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor, typer_option_linke_turbidity_factor_series
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
# refracted_solar_zenith: Annotated[
# float | None, typer_option_refracted_solar_zenith
# ] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[float | None, typer_option_albedo] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel, typer_option_solar_position_model
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
sun_horizon_position: Annotated[
List[SunHorizonPositionModel], typer_option_sun_horizon_position
] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel, typer_option_solar_incidence_model
] = SolarIncidenceModel.iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool, typer_option_zero_negative_solar_incidence_angle
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[float, typer_option_solar_constant] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
horizon_profile: Annotated[DataArray | None, typer_option_horizon_profile] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model
] = ShadingModel.pvgis, # for performance analysis : should be one !
shading_states: Annotated[
List[ShadingState], typer_option_shading_state
] = [ShadingState.all],
photovoltaic_module: Annotated[
PhotovoltaicModuleModel, typer_option_photovoltaic_module_model
] = PHOTOVOLTAIC_MODULE_DEFAULT, # PhotovoltaicModuleModel.CSI_FREE_STANDING,
peak_power: Annotated[float, typer_option_photovoltaic_module_peak_power] = 1,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel, typer_option_pv_power_algorithm
] = PhotovoltaicModulePerformanceModel.king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm, typer_option_module_temperature_algorithm
] = ModuleTemperatureAlgorithm.faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
angle_output_units: Annotated[str, typer_option_angle_output_units] = RADIANS,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
nomenclature: Annotated[
bool, typer_option_nomenclature
] = NOMENCLATURE_FLAG_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = METADATA_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = QuickResponseCode.NoneValue,
profile: Annotated[bool, typer_option_profiling] = cPROFILE_FLAG_DEFAULT,
):
"""Estimate the photovoltaic power output for a location and a moment or period
in time.
Estimate the photovoltaic power over a time series or an arbitrarily
aggregated energy production of a PV system connected to the electricity
grid (without battery storage) based on broadband solar irradiance, ambient
temperature and wind speed.
Notes
-----
The optional input parameters `global_horizontal_irradiance` and
`direct_horizontal_irradiance` accept any Xarray-support data file format
and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the
functions that calculate the diffuse and direct components, are defined as
`global_horizontal_component` and `direct_horizontal_component`. This is to
avoid confusion at the function level. For example, the function
`calculate_diffuse_inclined_irradiance_series()` can read the direct
horizontal component (thus the name of it `direct_horizontal_component` as
well as simulate it. The point is to make it clear that if the
`direct_horizontal_component` parameter is True (which means the user has
provided an external dataset), then read it using the
`select_time_series()` function.
"""
# if global_horizontal_irradiance + direct_horizontal_irradiance are Path objects:
if isinstance(global_horizontal_irradiance, (str, Path)) and isinstance(
direct_horizontal_irradiance, (str, Path)
): # NOTE This is in the case everything is pathlike
global_horizontal_irradiance, direct_horizontal_irradiance = (
read_horizontal_irradiance_components_from_sarah(
shortwave=global_horizontal_irradiance,
direct=direct_horizontal_irradiance,
longitude=convert_float_to_degrees_if_requested(longitude, DEGREES),
latitude=convert_float_to_degrees_if_requested(latitude, DEGREES),
timestamps=timestamps,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
multi_thread=multi_thread,
# multi_thread=False,
verbose=verbose,
log=log,
)
)
temperature_series, wind_speed_series, spectral_factor_series = get_time_series(
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
spectral_factor_series=spectral_factor_series,
longitude=Longitude(value=longitude, unit='radians'),
latitude=Latitude(values=latitude, units='radians'),
timestamps=timestamps,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
dtype=dtype,
array_backend=array_backend,
multi_thread=multi_thread,
verbose=verbose,
log=log,
)
photovoltaic_power_output_series = calculate_photovoltaic_power_output_series(
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=surface_orientation,
surface_tilt=surface_tilt,
timestamps=timestamps,
timezone=timezone,
global_horizontal_irradiance=global_horizontal_irradiance,
direct_horizontal_irradiance=direct_horizontal_irradiance,
spectral_factor_series=spectral_factor_series,
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
linke_turbidity_factor_series=linke_turbidity_factor_series,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
# unrefracted_solar_zenith=unrefracted_solar_zenith,
albedo=albedo,
apply_reflectivity_factor=apply_reflectivity_factor,
solar_position_model=solar_position_model,
sun_horizon_position=sun_horizon_position,
solar_incidence_model=solar_incidence_model,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
solar_time_model=solar_time_model,
solar_constant=solar_constant,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
horizon_profile=horizon_profile, # Review naming please ?
shading_model=shading_model,
shading_states=shading_states,
# angle_output_units=angle_output_units,
photovoltaic_module_type=photovoltaic_module_type,
photovoltaic_module=photovoltaic_module,
peak_power=peak_power,
system_efficiency=system_efficiency,
power_model=power_model,
temperature_model=temperature_model,
efficiency=efficiency,
dtype=dtype,
array_backend=array_backend,
# multi_thread=multi_thread,
verbose=verbose,
log=log,
fingerprint=fingerprint,
profile=profile,
validate_output=validate_output,
) # Re-Design Me ! ------------------------------------------------
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if quick_response_code.value != QuickResponseCode.NoneValue:
from pvgisprototype.cli.print.qr import print_quick_response_code
print_quick_response_code(
dictionary=photovoltaic_power_output_series.output,
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=True,
surface_tilt=True,
timestamps=timestamps,
rounding_places=rounding_places,
output_type=quick_response_code,
)
return
if not quiet:
if verbose > 0:
from pvgisprototype.cli.print.irradiance.data import print_irradiance_table_2
print_irradiance_table_2(
title=photovoltaic_power_output_series.title + f" series [{POWER_UNIT}]",
irradiance_data=photovoltaic_power_output_series.output,
# rear_side_irradiance_data=rear_side_photovoltaic_power_output_series.output if rear_side_photovoltaic_power_output_series else None,
longitude=longitude,
latitude=latitude,
elevation=elevation,
timestamps=timestamps,
timezone=timezone,
rounding_places=rounding_places,
index=index,
verbose=verbose,
)
else:
# # Redesign Me : Handle this "upstream", avoid alterations here ?
# if photovoltaic_module_type == PhotovoltaicModuleType.Bifacial:
# photovoltaic_power_output_series.value += (
# rear_side_photovoltaic_power_output_series.value
# )
# # ------------------------- Better handling of rounding vs dtype ?
print(
",".join(
round_float_values(
photovoltaic_power_output_series.value.flatten(),
rounding_places,
).astype(str)
# photovoltaic_power_output_series.value.flatten().astype(str)
)
)
if statistics:
from pvgisprototype.cli.print.series import print_series_statistics
print_series_statistics(
data_array=photovoltaic_power_output_series.value,
timestamps=timestamps,
groupby=groupby,
title=photovoltaic_power_output_series.title,
rounding_places=rounding_places,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_data_array_series
extra_data_array = (
[rear_side_photovoltaic_power_output_series.value]
if "rear_side_photovoltaic_power_output_series" in locals()
# and rear_side_photovoltaic_power_output_series.value is not None
and rear_side_photovoltaic_power_output_series is not None
else []
)
orientation = (
[surface_orientation, rear_side_surface_orientation]
if "rear_side_surface_orientation" in locals()
else [surface_orientation]
)
tilt = (
[surface_tilt, rear_side_surface_tilt]
if "rear_side_surface_tilt" in locals()
else [surface_tilt]
)
uniplot_data_array_series(
data_array=photovoltaic_power_output_series.value,
# list_extra_data_arrays=extra_data_array,
longitude=longitude,
latitude=latitude,
orientation=orientation, # [surface_orientation, rear_side_surface_orientation],
tilt=tilt, # [surface_tilt, rear_side_surface_tilt],
timestamps=timestamps,
resample_large_series=resample_large_series,
lines=True,
supertitle=photovoltaic_power_output_series.supertitle,
title=photovoltaic_power_output_series.title,
label=photovoltaic_power_output_series.label,
# extra_legend_labels=["Rear-side Photovoltaic Power"],
unit=POWER_UNIT,
terminal_width_fraction=terminal_width_fraction,
)
if metadata:
import click
from pvgisprototype.cli.print.metadata import print_command_metadata
print_command_metadata(context=click.get_current_context())
if fingerprint:
from pvgisprototype.cli.print.fingerprint import print_finger_hash
print_finger_hash(dictionary=photovoltaic_power_output_series.output)
# Call write_irradiance_csv() last : it modifies the input dictionary !
if csv:
from pvgisprototype.cli.write import write_irradiance_csv
write_irradiance_csv(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dictionary=photovoltaic_power_output_series.output,
filename=csv,
index=index,
)
broadband_multiple_surfaces ¶
CLI module to calculate the photovoltaic power output over a location for a period in time.
Functions:
| Name | Description |
|---|---|
photovoltaic_power_output_series_from_multiple_surfaces | Estimate the sum of photovoltaic output for multiple solar surface |
photovoltaic_power_output_series_from_multiple_surfaces ¶
photovoltaic_power_output_series_from_multiple_surfaces(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
list | None, typer_option_surface_orientation_multi
] = [float(SURFACE_ORIENTATION_DEFAULT)],
surface_tilt: Annotated[
list | None, typer_option_surface_tilt_multi
] = [float(SURFACE_TILT_DEFAULT)],
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now()),
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = None,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
global_horizontal_irradiance: Annotated[
Path | None,
typer_option_global_horizontal_irradiance,
] = None,
direct_horizontal_irradiance: Annotated[
Path | None,
typer_option_direct_horizontal_irradiance,
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries,
typer_argument_spectral_factor_series,
] = SPECTRAL_FACTOR_DEFAULT,
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor,
typer_option_linke_turbidity_factor_series,
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[
float | None, typer_option_albedo
] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel,
typer_option_solar_position_model,
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
sun_horizon_position: Annotated[
List[SunHorizonPositionModel],
typer_option_sun_horizon_position,
] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel,
typer_option_solar_incidence_model,
] = iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool,
typer_option_zero_negative_solar_incidence_angle,
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
horizon_profile: Annotated[
DataArray | None, typer_option_horizon_profile
] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model
] = pvgis,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[
float, typer_option_solar_constant
] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = RADIANS,
photovoltaic_module: Annotated[
PhotovoltaicModuleModel,
typer_option_photovoltaic_module_model,
] = PHOTOVOLTAIC_MODULE_DEFAULT,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel,
typer_option_pv_power_algorithm,
] = king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm,
typer_option_module_temperature_algorithm,
] = faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = NoneValue,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
profile: Annotated[
bool, typer_option_profiling
] = cPROFILE_FLAG_DEFAULT,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
)
Estimate the sum of photovoltaic output for multiple solar surface setups for a location and a moment or period in time.
Estimate the sum of photovoltaic power over a time series or an arbitrarily aggregated energy production of multiple PV system setups, i.e. different tilts and orientations, connected to the electricity grid (without battery storage) based on broadband solar irradiance, ambient temperature and wind speed.
Notes
The optional input parameters global_horizontal_irradiance and direct_horizontal_irradiance accept any Xarray-support data file format and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the functions that calculate the diffuse and direct components, are defined as global_horizontal_component and direct_horizontal_component. This is to avoid confusion at the function level. For example, the function calculate_diffuse_inclined_irradiance_series() can read the direct horizontal component (thus the name of it direct_horizontal_component as well as simulate it. The point is to make it clear that if the direct_horizontal_component parameter is True (which means the user has provided an external dataset), then read it using the select_time_series() function.
Source code in pvgisprototype/cli/power/broadband_multiple_surfaces.py
@log_function_call
def photovoltaic_power_output_series_from_multiple_surfaces(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
list | None, typer_option_surface_orientation_multi
] = [float(SURFACE_ORIENTATION_DEFAULT)],
surface_tilt: Annotated[list | None, typer_option_surface_tilt_multi] = [
float(SURFACE_TILT_DEFAULT)
],
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(Timestamp.now()),
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = None,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
global_horizontal_irradiance: Annotated[
Path | None, typer_option_global_horizontal_irradiance
] = None,
direct_horizontal_irradiance: Annotated[
Path | None, typer_option_direct_horizontal_irradiance
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries, typer_argument_spectral_factor_series
] = SPECTRAL_FACTOR_DEFAULT, # Accept also list of float values ?
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor, typer_option_linke_turbidity_factor_series
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[float | None, typer_option_albedo] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel, typer_option_solar_position_model
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
sun_horizon_position: Annotated[
List[SunHorizonPositionModel], typer_option_sun_horizon_position
] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel, typer_option_solar_incidence_model
] = SolarIncidenceModel.iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool, typer_option_zero_negative_solar_incidence_angle
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
horizon_profile: Annotated[DataArray | None, typer_option_horizon_profile] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model] = ShadingModel.pvgis, # for performance analysis : should be one !
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[float, typer_option_solar_constant] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
angle_output_units: Annotated[str, typer_option_angle_output_units] = RADIANS,
photovoltaic_module: Annotated[
PhotovoltaicModuleModel, typer_option_photovoltaic_module_model
] = PHOTOVOLTAIC_MODULE_DEFAULT, # PhotovoltaicModuleModel.CSI_FREE_STANDING,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel, typer_option_pv_power_algorithm
] = PhotovoltaicModulePerformanceModel.king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm, typer_option_module_temperature_algorithm
] = ModuleTemperatureAlgorithm.faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = QuickResponseCode.NoneValue,
metadata: Annotated[bool, typer_option_command_metadata] = False,
profile: Annotated[bool, typer_option_profiling] = cPROFILE_FLAG_DEFAULT,
validate_output: Annotated[bool, typer_option_validate_output] = VALIDATE_OUTPUT_DEFAULT,
):
"""Estimate the sum of photovoltaic output for multiple solar surface
setups for a location and a moment or period in time.
Estimate the sum of photovoltaic power over a time series or an arbitrarily
aggregated energy production of multiple PV system setups, i.e. different
tilts and orientations, connected to the electricity grid (without battery
storage) based on broadband solar irradiance, ambient temperature and wind
speed.
Notes
-----
The optional input parameters `global_horizontal_irradiance` and
`direct_horizontal_irradiance` accept any Xarray-support data file format
and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the
functions that calculate the diffuse and direct components, are defined as
`global_horizontal_component` and `direct_horizontal_component`. This is to
avoid confusion at the function level. For example, the function
`calculate_diffuse_inclined_irradiance_series()` can read the direct
horizontal component (thus the name of it `direct_horizontal_component` as
well as simulate it. The point is to make it clear that if the
`direct_horizontal_component` parameter is True (which means the user has
provided an external dataset), then read it using the
`select_time_series()` function.
"""
if len(surface_tilt) != len(surface_orientation):
from pvgisprototype.api.series.hardcodings import exclamation_mark
logger.error(
f"{exclamation_mark} Aborting as length of --surface_orientation and --surface_tilt is not the same!",
alt=f"{exclamation_mark} [red]Aborting[/red] as [red]length[/red] [code]--surface-orientation[/code] and [code]--surface-tilt[/code] [red]is not the same[/red]!",
)
return
if isinstance(global_horizontal_irradiance, (str, Path)) and isinstance(
direct_horizontal_irradiance, (str, Path)
): # NOTE This is in the case everything is pathlike
horizontal_irradiance_components = (
read_horizontal_irradiance_components_from_sarah(
shortwave=global_horizontal_irradiance,
direct=direct_horizontal_irradiance,
longitude=convert_float_to_degrees_if_requested(longitude, DEGREES),
latitude=convert_float_to_degrees_if_requested(latitude, DEGREES),
timestamps=timestamps,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
multi_thread=multi_thread,
# multi_thread=False,
verbose=verbose,
log=log,
)
)
global_horizontal_irradiance = horizontal_irradiance_components[
GLOBAL_HORIZONTAL_IRRADIANCE_COLUMN_NAME
]
direct_horizontal_irradiance = horizontal_irradiance_components[
DIRECT_HORIZONTAL_IRRADIANCE_COLUMN_NAME
]
temperature_series, wind_speed_series, spectral_factor_series = get_time_series(
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
spectral_factor_series=spectral_factor_series,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
dtype=dtype,
array_backend=array_backend,
multi_thread=multi_thread,
verbose=verbose,
log=log,
)
photovoltaic_power_output_series = calculate_photovoltaic_power_output_series_from_multiple_surfaces(
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientations=surface_orientation,
surface_tilts=surface_tilt,
timestamps=timestamps,
timezone=timezone,
global_horizontal_irradiance=global_horizontal_irradiance,
direct_horizontal_irradiance=direct_horizontal_irradiance,
spectral_factor_series=spectral_factor_series,
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
dtype=dtype,
array_backend=array_backend,
multi_thread=multi_thread,
linke_turbidity_factor_series=linke_turbidity_factor_series,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
# unrefracted_solar_zenith=unrefracted_solar_zenith,
albedo=albedo,
apply_reflectivity_factor=apply_reflectivity_factor,
solar_position_model=solar_position_model,
sun_horizon_position=sun_horizon_position,
solar_incidence_model=solar_incidence_model,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
solar_time_model=solar_time_model,
solar_constant=solar_constant,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
angle_output_units=angle_output_units,
horizon_profile=horizon_profile,
shading_model=shading_model,
photovoltaic_module=photovoltaic_module,
system_efficiency=system_efficiency,
power_model=power_model,
temperature_model=temperature_model,
efficiency=efficiency,
verbose=verbose,
log=log,
fingerprint=fingerprint,
profile=profile,
validate_output=validate_output,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if quick_response_code.value != QuickResponseCode.NoneValue:
from pvgisprototype.cli.print.qr import print_quick_response_code
print_quick_response_code(
dictionary=photovoltaic_power_output_series.output,
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=True,
surface_tilt=True,
timestamps=timestamps,
rounding_places=rounding_places,
)
return
if not quiet:
if verbose > 0:
from pvgisprototype.cli.print.irradiance.data import print_irradiance_table_2
print_irradiance_table_2(
title=photovoltaic_power_output_series.title + f" series [{POWER_UNIT}]",
irradiance_data=photovoltaic_power_output_series.output,
longitude=longitude,
latitude=latitude,
elevation=elevation,
timestamps=timestamps,
timezone=timezone,
rounding_places=rounding_places,
index=index,
verbose=verbose,
)
else:
flat_list = photovoltaic_power_output_series.value.flatten().astype(str)
csv_str = ",".join(flat_list)
print(csv_str)
if statistics:
from pvgisprototype.cli.print.series import print_series_statistics
print_series_statistics(
data_array=photovoltaic_power_output_series.series,
timestamps=timestamps,
groupby=groupby,
title="Photovoltaic power output",
)
if uniplot:
from pvgisprototype.api.plot import uniplot_data_array_series
individual_series = [
series.value
for series in photovoltaic_power_output_series.individual_series
]
surface_orientation = [
convert_float_to_degrees_if_requested(orientation, angle_output_units)
for orientation in surface_orientation
]
surface_tilt = [
convert_float_to_degrees_if_requested(tilt, angle_output_units)
for tilt in surface_tilt
]
surface_orientation = round_float_values(surface_orientation, rounding_places)
surface_tilt = round_float_values(surface_tilt, rounding_places)
individual_labels = [
f"Orientation, Tilt : {orientation}°, {tilt}°"
for orientation, tilt in zip(surface_orientation, surface_tilt)
]
uniplot_data_array_series(
data_array=photovoltaic_power_output_series.value,
list_extra_data_arrays=individual_series,
timestamps=timestamps,
resample_large_series=resample_large_series,
lines=True,
supertitle="Photovoltaic Power Output Series",
title="Photovoltaic power output from multiple surfaces",
label="Sum of Photovoltaic Power",
extra_legend_labels=individual_labels,
unit=POWER_UNIT,
terminal_width_fraction=terminal_width_fraction,
)
if fingerprint:
from pvgisprototype.cli.print.fingerprint import print_finger_hash
print_finger_hash(dictionary=photovoltaic_power_output_series.output)
if metadata:
import click
from pvgisprototype.cli.print.metadata import print_command_metadata
print_command_metadata(context=click.get_current_context())
# Call write_irradiance_csv() last : it modifies the input dictionary !
if csv:
from pvgisprototype.cli.write import write_irradiance_csv
write_irradiance_csv(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dictionary=photovoltaic_power_output_series.output,
filename=csv,
index=index,
)
broadband_rear_side ¶
CLI module to calculate the photovoltaic power output over a location for a period in time.
Functions:
| Name | Description |
|---|---|
rear_side_photovoltaic_power_output_series | Estimate the photovoltaic power output for a location and a moment or period |
rear_side_photovoltaic_power_output_series ¶
rear_side_photovoltaic_power_output_series(
ctx: Context,
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None,
typer_argument_rear_side_surface_orientation,
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_argument_rear_side_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[
DatetimeIndex | None, typer_argument_timestamps
] = str(now()),
timezone: Annotated[
ZoneInfo | None, typer_option_timezone
] = None,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
global_horizontal_irradiance: Annotated[
Path | None,
typer_option_global_horizontal_irradiance,
] = None,
direct_horizontal_irradiance: Annotated[
Path | None,
typer_option_direct_horizontal_irradiance,
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries,
typer_argument_spectral_factor_series,
] = SPECTRAL_FACTOR_DEFAULT,
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor,
typer_option_linke_turbidity_factor_series,
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[
float | None, typer_option_albedo
] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel,
typer_option_solar_position_model,
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
sun_horizon_position: Annotated[
List[SunHorizonPositionModel],
typer_option_sun_horizon_position,
] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel,
typer_option_solar_incidence_model,
] = iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool,
typer_option_zero_negative_solar_incidence_angle,
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[
float, typer_option_solar_constant
] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
horizon_profile: Annotated[
DataArray | None, typer_option_horizon_profile
] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model
] = pvgis,
shading_states: Annotated[
List[ShadingState], typer_option_shading_state
] = SHADING_STATE_DEFAULT,
photovoltaic_module: Annotated[
PhotovoltaicModuleModel,
typer_option_photovoltaic_module_model,
] = PHOTOVOLTAIC_MODULE_DEFAULT,
peak_power: Annotated[
float, typer_option_photovoltaic_module_peak_power
] = 1,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel,
typer_option_pv_power_algorithm,
] = king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm,
typer_option_module_temperature_algorithm,
] = faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = REAR_SIDE_EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = RADIANS,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
nomenclature: Annotated[
bool, typer_option_nomenclature
] = NOMENCLATURE_FLAG_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = METADATA_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = NoneValue,
profile: Annotated[
bool, typer_option_profiling
] = cPROFILE_FLAG_DEFAULT,
)
Estimate the photovoltaic power output for a location and a moment or period in time.
Estimate the photovoltaic power over a time series or an arbitrarily aggregated energy production of a PV system connected to the electricity grid (without battery storage) based on broadband solar irradiance, ambient temperature and wind speed.
Notes
The optional input parameters global_horizontal_irradiance and direct_horizontal_irradiance accept any Xarray-support data file format and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the functions that calculate the diffuse and direct components, are defined as global_horizontal_component and direct_horizontal_component. This is to avoid confusion at the function level. For example, the function calculate_diffuse_inclined_irradiance_series() can read the direct horizontal component (thus the name of it direct_horizontal_component as well as simulate it. The point is to make it clear that if the direct_horizontal_component parameter is True (which means the user has provided an external dataset), then read it using the select_time_series() function.
Source code in pvgisprototype/cli/power/broadband_rear_side.py
@log_function_call
def rear_side_photovoltaic_power_output_series(
ctx: typer.Context,
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None, typer_argument_rear_side_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_argument_rear_side_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[DatetimeIndex | None, typer_argument_timestamps] = str(
Timestamp.now()
),
timezone: Annotated[ZoneInfo | None, typer_option_timezone] = None,
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
global_horizontal_irradiance: Annotated[
Path | None, typer_option_global_horizontal_irradiance
] = None,
direct_horizontal_irradiance: Annotated[
Path | None, typer_option_direct_horizontal_irradiance
] = None,
spectral_factor_series: Annotated[
SpectralFactorSeries, typer_argument_spectral_factor_series
] = SPECTRAL_FACTOR_DEFAULT, # Accept also list of float values ?
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor, typer_option_linke_turbidity_factor_series
] = LinkeTurbidityFactor(),
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[float | None, typer_option_albedo] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = ANGULAR_LOSS_FACTOR_FLAG_DEFAULT,
solar_position_model: Annotated[
SolarPositionModel, typer_option_solar_position_model
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
sun_horizon_position: Annotated[
List[SunHorizonPositionModel], typer_option_sun_horizon_position
] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel, typer_option_solar_incidence_model
] = SolarIncidenceModel.iqbal,
zero_negative_solar_incidence_angle: Annotated[
bool, typer_option_zero_negative_solar_incidence_angle
] = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[float, typer_option_solar_constant] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
horizon_profile: Annotated[DataArray | None, typer_option_horizon_profile] = None,
shading_model: Annotated[
ShadingModel, typer_option_shading_model
] = ShadingModel.pvgis, # for performance analysis : should be one !
shading_states: Annotated[
List[ShadingState], typer_option_shading_state
] = SHADING_STATE_DEFAULT,
photovoltaic_module: Annotated[
PhotovoltaicModuleModel, typer_option_photovoltaic_module_model
] = PHOTOVOLTAIC_MODULE_DEFAULT, # PhotovoltaicModuleModel.CSI_FREE_STANDING,
peak_power: Annotated[float, typer_option_photovoltaic_module_peak_power] = 1,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT, # REAR_SIDE_SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel, typer_option_pv_power_algorithm
] = PhotovoltaicModulePerformanceModel.king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm, typer_option_module_temperature_algorithm
] = ModuleTemperatureAlgorithm.faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = REAR_SIDE_EFFICIENCY_FACTOR_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
multi_thread: Annotated[
bool, typer_option_multi_thread
] = MULTI_THREAD_FLAG_DEFAULT,
angle_output_units: Annotated[str, typer_option_angle_output_units] = RADIANS,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
nomenclature: Annotated[
bool, typer_option_nomenclature
] = NOMENCLATURE_FLAG_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
validate_output: Annotated[
bool, typer_option_validate_output
] = VALIDATE_OUTPUT_DEFAULT,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = METADATA_FLAG_DEFAULT,
quick_response_code: Annotated[
QuickResponseCode, typer_option_quick_response
] = QuickResponseCode.NoneValue,
profile: Annotated[bool, typer_option_profiling] = cPROFILE_FLAG_DEFAULT,
):
"""Estimate the photovoltaic power output for a location and a moment or period
in time.
Estimate the photovoltaic power over a time series or an arbitrarily
aggregated energy production of a PV system connected to the electricity
grid (without battery storage) based on broadband solar irradiance, ambient
temperature and wind speed.
Notes
-----
The optional input parameters `global_horizontal_irradiance` and
`direct_horizontal_irradiance` accept any Xarray-support data file format
and mean the global and direct irradiance on the horizontal plane.
Inside the API, however, and for legibility, the same parameters in the
functions that calculate the diffuse and direct components, are defined as
`global_horizontal_component` and `direct_horizontal_component`. This is to
avoid confusion at the function level. For example, the function
`calculate_diffuse_inclined_irradiance_series()` can read the direct
horizontal component (thus the name of it `direct_horizontal_component` as
well as simulate it. The point is to make it clear that if the
`direct_horizontal_component` parameter is True (which means the user has
provided an external dataset), then read it using the
`select_time_series()` function.
"""
temperature_series, wind_speed_series, spectral_factor_series = get_time_series(
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
spectral_factor_series=spectral_factor_series,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
dtype=dtype,
array_backend=array_backend,
multi_thread=multi_thread,
verbose=verbose,
log=log,
)
rear_side_photovoltaic_power_output_series = calculate_rear_side_photovoltaic_power_output_series(
longitude=longitude,
latitude=latitude,
elevation=elevation,
rear_side_surface_orientation=surface_orientation,
rear_side_surface_tilt=surface_tilt,
timestamps=timestamps,
timezone=timezone,
global_horizontal_irradiance=global_horizontal_irradiance,
direct_horizontal_irradiance=direct_horizontal_irradiance,
spectral_factor_series=spectral_factor_series,
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
linke_turbidity_factor_series=linke_turbidity_factor_series,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
albedo=albedo,
apply_reflectivity_factor=apply_reflectivity_factor,
solar_position_model=solar_position_model,
solar_incidence_model=solar_incidence_model,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
solar_time_model=solar_time_model,
solar_constant=solar_constant,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
horizon_profile=horizon_profile, # Review naming please ?
shading_model=shading_model,
angle_output_units=angle_output_units,
# photovoltaic_module_type=photovoltaic_module_type,
photovoltaic_module=photovoltaic_module,
peak_power=peak_power,
system_efficiency=system_efficiency,
power_model=power_model,
temperature_model=temperature_model,
rear_side_efficiency=efficiency,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
fingerprint=fingerprint,
profile=profile,
validate_output=validate_output,
) # Re-Design Me ! ------------------------------------------------
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if quick_response_code.value != QuickResponseCode.NoneValue:
from pvgisprototype.cli.print.qr import print_quick_response_code
print_quick_response_code(
dictionary=rear_side_photovoltaic_power_output_series.components,
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=True,
surface_tilt=True,
timestamps=timestamps,
rounding_places=rounding_places,
output_type=quick_response_code,
)
return
if not quiet:
if verbose > 0:
from pvgisprototype.cli.print.irradiance import print_irradiance_table_2
print_irradiance_table_2(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dictionary=rear_side_photovoltaic_power_output_series.components,
# title=rear_side_photovoltaic_power_output_series['Title'] + f" series {POWER_UNIT}",
rounding_places=rounding_places,
index=index,
surface_orientation=True,
surface_tilt=True,
verbose=verbose,
)
else:
# ------------------------- Better handling of rounding vs dtype ?
print(
",".join(
# round_float_values(
# rear_side_photovoltaic_power_output_series.value.flatten(),
# rounding_places,
# ).astype(str)
rear_side_photovoltaic_power_output_series.value.flatten().astype(
str
)
)
)
if statistics:
from pvgisprototype.cli.print.series import print_series_statistics
print_series_statistics(
data_array=rear_side_photovoltaic_power_output_series,
timestamps=timestamps,
groupby=groupby,
title="Photovoltaic power output",
rounding_places=rounding_places,
)
if uniplot:
from pvgisprototype.api.plot import uniplot_data_array_series
uniplot_data_array_series(
data_array=rear_side_photovoltaic_power_output_series.value,
timestamps=timestamps,
resample_large_series=resample_large_series,
lines=True,
supertitle="Photovoltaic Power Output Series",
title="Photovoltaic power output",
label="Photovoltaic Power",
extra_legend_labels=None,
unit=POWER_UNIT,
terminal_width_fraction=terminal_width_fraction,
)
if metadata:
import click
from pvgisprototype.cli.print.metadata import print_command_metadata
print_command_metadata(context=click.get_current_context())
if fingerprint:
from pvgisprototype.cli.print.fingerprint import print_finger_hash
print_finger_hash(
dictionary=rear_side_photovoltaic_power_output_series.components
)
# Call write_irradiance_csv() last : it modifies the input dictionary !
if csv:
from pvgisprototype.cli.write import write_irradiance_csv
write_irradiance_csv(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dictionary=rear_side_photovoltaic_power_output_series.components,
filename=csv,
index=index,
)
energy_ ¶
energy grid - fixed - tracking off-grid
Functions:
| Name | Description |
|---|---|
estimate_grid_connected_pv | Estimate the energy production of a PV system connected to the electricity grid |
estimate_offgrid_pv | Estimate the energy production of a PV system that is not connected to |
estimate_tracking_pv | Estimate the energy production of a tracking PV system connected to the electricity grid |
estimate_grid_connected_pv ¶
estimate_grid_connected_pv(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
peak_power: Annotated[
float,
Argument(
rich_help_panel=Required,
help="The installed peak PV power in kWp",
min=0,
max=100000000,
),
],
loss: Annotated[
float,
Argument(
rich_help_panel=Preset,
help="System losses in %",
min=0,
max=100,
),
] = 14,
solar_radiation_database: Annotated[
str | None,
Argument(
rich_help_panel=Preset,
help="Solar radiation database with hourly time resolution",
),
] = "PVGIS-SARAH2",
consider_shadows: Annotated[
bool,
Argument(
rich_help_panel=Preset,
help="Calculate effect of horizon shadowing",
),
] = True,
horizon_heights: Annotated[
List[float], typer_argument_horizon_heights
] = None,
pv_techonology: Annotated[
str | None, typer_argument_pv_technology
] = None,
mounting_type: Annotated[
str | None, typer_argument_mounting_type
] = "free",
surface_orientation: Annotated[
float, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float, typer_argument_surface_tilt
] = SURFACE_TILT_DEFAULT,
optimise_surface_tilt: Annotated[
bool, typer_option_optimise_surface_tilt
] = OPTIMISE_SURFACE_TILT_FLAG_DEFAULT,
optimise_surface_geometry: Annotated[
bool, typer_option_optimise_surface_geometry
] = OPTIMISE_SURFACE_GEOMETRY_FLAG_DEFAULT,
single_axis_system: Annotated[
bool,
Argument(
rich_help_panel=Optional,
help="Consider a single axis PV system -- Remove Me and improve single_axis_inclination!",
),
] = False,
single_axis_inclination: Annotated[
float,
Argument(
rich_help_panel=Optional,
help="Inclination for a single axis PV system",
min=0,
max=90,
),
] = 0,
optimise_single_axis_inclination: Annotated[
bool,
Argument(
rich_help_panel=Optional,
help="Optimise inclination for a single axis PV system",
),
] = False,
vertical_axis_system: Annotated[
bool,
Argument(
rich_help_panel=Optional,
help="Consider a single vertical axis PV system -- Remove Me and improve vertical_axis_inclination!",
),
] = False,
vertical_axis_inclination: Annotated[
float,
Argument(
rich_help_panel=Optional,
help="Inclination for a single axis PV system",
min=0,
max=90,
),
] = 0,
optimise_vertical_axis_inclination: Annotated[
bool,
Argument(
rich_help_panel=Optional,
help="Optimise inclination for a single vertical axis PV system",
),
] = False,
two_axis_system: Annotated[
bool,
Argument(
rich_help_panel=Optional,
help="Consider a two-axis tracking PV system -- Review Me!",
),
] = False,
electricity_price: Annotated[
bool,
Argument(
rich_help_panel=Optional,
help="Calculate the PV electricity price (kwh/year) for the system cost in the user requested currency -- Review Me!",
),
] = False,
cost: Annotated[
float,
Argument(
rich_help_panel=Optional,
help="Total cost of installing the PV system [custom currency]",
min=0,
max=100000000,
),
] = 0,
interest: Annotated[
float,
Argument(
rich_help_panel=Optional,
help="Interest in %/year",
min=0,
max=100,
),
] = None,
lifetime: Annotated[
int,
Argument(
rich_help_panel=Optional,
help="Expected lifetime of the PV system in years",
min=0,
max=100,
),
] = 25,
output_format: Annotated[
str | None, Argument(help="Output format")
] = "csv",
)
Estimate the energy production of a PV system connected to the electricity grid
Performance of grid-connected PV systems. This tool makes it possible to estimate the average monthly and yearly energy production of a PV system connected to the electricity grid, without battery storage. The calculation takes into account the solar radiation, temperature, wind speed and type of PV module. The user can choose how the modules are mounted, whether on a free-standing rack mounting, sun-tracking mountings or integrated in a building surface. PVGIS can also calculate the optimum slope and orientation that maximizes the yearly energy production.
Requires following data:
- elevation
- horizon
- dem_era5
- tgrad_bin
- sis
- sid
- temperature (ERA5 t2m)
- wind speed (ERA5 w2m)
- pv coefficients (current file: pvtech.coeffs)
- pv coefficients for bifacial? (current file: pvtech.coeffs_bipv)
- spectral correction data (current file: pvtech_spectraldata.bin)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
longitude | Annotated[float, typer_argument_longitude] | | required |
latitude | Annotated[float, typer_argument_latitude] | | required |
peak_power | Annotated[float, Argument(rich_help_panel=Required, help='The installed peak PV power in kWp', min=0, max=100000000)] | | required |
loss | Annotated[float, Argument(rich_help_panel=Preset, help='System losses in %', min=0, max=100)] | | 14 |
solar_radiation_database | Annotated[str | None, Argument(rich_help_panel=Preset, help='Solar radiation database with hourly time resolution')] | | 'PVGIS-SARAH2' |
consider_shadows | Annotated[bool, Argument(rich_help_panel=Preset, help='Calculate effect of horizon shadowing')] | | True |
horizon_heights | Annotated[List[float], typer_argument_horizon_heights] | | None |
pv_techonology | Annotated[str | None, typer_argument_pv_technology] | | None |
mounting_type | Annotated[str | None, typer_argument_mounting_type] | | 'free' |
inclination | | required | |
orientation | | required | |
optimise_inclination | | required | |
optimise_angles | | required | |
single_axis_system | Annotated[bool, Argument(rich_help_panel=Optional, help='Consider a single axis PV system -- Remove Me and improve single_axis_inclination!')] | | False |
single_axis_inclination | Annotated[float, Argument(rich_help_panel=Optional, help='Inclination for a single axis PV system', min=0, max=90)] | | 0 |
optimise_single_axis_inclination | Annotated[bool, Argument(rich_help_panel=Optional, help='Optimise inclination for a single axis PV system')] | | False |
vertical_axis_system | Annotated[bool, Argument(rich_help_panel=Optional, help='Consider a single vertical axis PV system -- Remove Me and improve vertical_axis_inclination!')] | | False |
vertical_axis_inclination | Annotated[float, Argument(rich_help_panel=Optional, help='Inclination for a single axis PV system', min=0, max=90)] | | 0 |
optimise_vertical_axis_inclination | Annotated[bool, Argument(rich_help_panel=Optional, help='Optimise inclination for a single vertical axis PV system')] | | False |
two_axis_system | Annotated[bool, Argument(rich_help_panel=Optional, help='Consider a two-axis tracking PV system -- Review Me!')] | | False |
electricity_price | Annotated[bool, Argument(rich_help_panel=Optional, help='Calculate the PV electricity price (kwh/year) for the system cost in the user requested currency -- Review Me!')] | | False |
cost | Annotated[float, Argument(rich_help_panel=Optional, help='Total cost of installing the PV system [custom currency]', min=0, max=100000000)] | | 0 |
interest | Annotated[float, Argument(rich_help_panel=Optional, help='Interest in %/year', min=0, max=100)] | | None |
lifetime | Annotated[int, Argument(rich_help_panel=Optional, help='Expected lifetime of the PV system in years', min=0, max=100)] | | 25 |
output_format | Annotated[str | None, Argument(help='Output format')] | | 'csv' |
Other Parameters:
| Name | Type | Description |
|---|---|---|
only_seldom_used_keyword | int | Infrequently used parameters can be described under this optional section to prevent cluttering the Parameters section. |
**kwargs | dict | Other infrequently used keyword arguments. Note that all keyword arguments appearing after the first parameter specified under the Other Parameters section, should also be described under this section. |
Returns:
| Type | Description |
|---|---|
str | PV calculation result based on the specified input parameters |
Raises:
| Type | Description |
|---|---|
BadException | Because you shouldn't have done that. |
See Also
estimate_energy.offgrid : Relationship (optional).
Notes
The following notes are sourced from:
- the manual of PVcalc
- the web GUI
- the original C/C++ program
rsun_standalone_hourly_opt
Original Parameters:
- lat (float): Latitude in decimal degrees, south is negative. (Required)
-
lon (float): Longitude in decimal degrees, west is negative. (Required)
- Input latitude and longitude
Latitude and longitude can be input in the format DD:MM:SSA where DD is the degrees, MM the arc-minutes, SS the arc-seconds and A the hemisphere (N, S, E, W).
Latitude and longitude can also be input as decimal values, so for instance 45°15'N should be input as 45.25. Latitudes south of the equator are input as negative values, north are positive. Longitudes west of the 0° meridian should be given as negative values, eastern values are positive.
-
peakpower (float): Nominal power of the PV system in kW. (Required)
- Installed peak PV power [kWp] - Peak power
This is the power that the manufacturer declares that the PV array can produce under standard test conditions, which are a constant 1000W of solar irradiance per square meter in the plane of the array, at an array temperature of 25°C. The peak power should be entered in kilowatt-peak (kWp). If you do not know the declared peak power of your modules but instead know the area of the modules (in m2) and the declared conversion efficiency (in percent), you can calculate the peak power as power (kWp) = 1 kW/m2 * area * efficiency / 100. See more explanation in the FAQ
-
loss (float): Sum of system losses in percent. (Required)
- Estimated system losses
The estimated system losses are all the losses in the system, which cause the power actually delivered to the electricity grid to be lower than the power produced by the PV modules. There are several causes for this loss, such as losses in cables, power inverters, dirt (sometimes snow) on the modules and so on. Over the years the modules also tend to lose a bit of their power, so the average yearly output over the lifetime of the system will be a few percent lower than the output in the first years.
We have given a default value of 14% for the overall losses. If you have a good idea that your value will be different (maybe due to a really high-efficiency inverter) you may reduce this value a little.
-
raddatabase (str): Radiation database. (Default: "PVGIS-SARAH2")
-
Solar radiation databases
-
PVGIS offers four different solar radiation databases with hourly time resolution. At the moment, there are three satellite-based databases:
-
PVGIS-SARAH2 (0.05º x 0.05º) Database produced by CM SAF to replace SARAH-1 (PVGIS-SARAH). It covers Europe, Africa, most of Asia, and parts of South America. Temporal range: 2005-2020.
-
PVGIS-SARAH* (0.05º x 0.05º) Database produced using the CM SAF algorithm. Similar coverage to SARAH-2. Temporal range: 2005-2016.
-
PVGIS-NSRDB (0.04º x 0.04º) Result of a collaboration with NREL (USA) under which the NSRDB solar radiation database was made available for PVGIS. Temporal range: 2005-2015.
In addition to these, there is also a reanalysis database available worldwide.
- PVGIS-ERA5 (0.25º x 0.25º) Latest global reanalysis of the ECMWF (ECMWF). Temporal range: 2005-2020.
Reanalyses solar radiation data generally have larger uncertainty than satellite-based databases. Therefore, we recommend using reanalysis data only where satellite-based data are missing or outdated. For more information about the databases and the accuracy, see the PVGIS web page on the calculation methods.
-
-
usehorizon (int): Consider shadows from high horizon (1) or not (0). (Default: 1)
- Calculated horizon
The solar radiation and PV output will change if there are local hills or mountains that block the light of the sun during some periods of the day. PVGIS can calculate the effect of this using data about ground elevation with a resolution of 3 arc-seconds (around 90m). This calculation does not take into account shadows from very nearby objects such as houses or trees. In this case you can upload your own horizon information.
It is normally a good idea to use the horizon shadowing option.
-
userhorizon (list): Height of the horizon at equidistant directions around the point of interest, in degrees. Starting at north and moving clockwise. (Optional)
- User-defined horizon
PVGIS includes a database of the horizon height around each point you can choose in the region. In this way, the calculation of PV performance can take into account the effects of mountains and hills casting shadows onto the PV system. The resolution of the horizon information is 3 arc-seconds (around 90m), so objects that are very near, such as houses or trees are not included. However, you have the possibility to upload your own information about the horizon height.
The horizon file to be uploaded to our web site should be a simple text file, such as you can create using a text editor (such as Notepad for Windows), or by exporting a spreadsheet as comma-separated values (.csv). The file name must have the extensions '.txt' or '.csv'.
In the file there should be one number per line, with each number representing the horizon height in degrees in a certain compass direction around the point of interest. The horizon height cannot be higher than 90 degrees, so if the file contains a value higher than that, it will be automatically replaced by 90.
The horizon heights in the file should be given in a clockwise direction starting at North; that is, from North, going to East, South, West, and back to North. The values are assumed to represent equal angular distance around the horizon. For instance, if you have 36 values in the file, PVGIS assumes that the first point is due north, the next is 10 degrees east of north, and so on, until the last point, 10 degrees west of north.
An example file can be found here. In this case, there are only 12 numbers in the file, corresponding to a horizon height for every 30 degrees around the horizon.
-
pvtechchoice (str): PV technology choice. (Optional)
- PV technology
The performance of PV modules depends on the temperature and on the solar irradiance, as well as on the spectrum of the sunlight, but the exact dependence varies between different types of PV modules. At the moment we can estimate the losses due to temperature and irradiance effects for the following types of modules:
1. crystalline silicon cells - thin film modules made from: 2. CIS or CIGS 3. Cadmium Telluride (CdTe)For other technologies (especially various amorphous technologies), this correction cannot be calculated here. If you choose one of the first three options here the calculation of performance will take into account the temperature dependence of the performance of the chosen technology. If you choose the other option (other/unknown), the calculation will assume a loss of 8% of power due to temperature effects (a generic value which was found to be reasonable for temperate climates). Note that the calculation of the effect of spectral variations is at the moment only available for crystalline silicon and for CdTe. The spectral effect cannot be considered yet for the areas only covered by the PVGIS-NSRDB database.
-
mountingplace (str): Type of mounting of the PV modules. Choices are: "free" or "building". (Default: "free")
-
fixed (bool): Calculate a fixed mounted system (1) or not (0). (Default: 1)
- Mounting position
For fixed (non-tracking) systems, the way the modules are mounted will have an influence on the temperature of the module, which in turn affects the efficiency. Experiments have shown that if the movement of air behind the modules is restricted, the modules can get considerably hotter (up to 15°C at 1000W/m2 of sunlight).
In the application there are two possibilities: free-standing, meaning that the modules are mounted on a rack with air flowing freely behind the modules; and roof added / building-integrated, which means that the modules are completely built into the structure of the wall or roof of a building, with little or no air movement behind the modules.
Some types of mounting are in between these two extremes, for instance if the modules are mounted on a roof with curved roof tiles, allowing air to move behind the modules. In such cases, the performance will be somewhere between the results of the two calculations that are possible here. For such cases, to be conservative, the roof added / building integrated option can be used.
-
angle (float): Inclination angle from horizontal plane of the fixed PV system. (Optional)
- Inclination angle or slope
This is the angle of the PV modules from the horizontal plane, for a fixed (non-tracking) mounting.
For some applications the slope and orientation angles will already be known, for instance if the PV modules are to be built into an existing roof. However, if you have the possibility to choose the slope and/or azimuth (orientation), this application can also calculate for you the optimal values for slope and orientation (assuming fixed angles for the entire year).
-
aspect (float): Orientation (azimuth) angle of the fixed PV system. (Optional)
- Orientation angle or azimuth
The azimuth, or orientation, is the angle of the PV modules relative to the direction due South. -90° is East, 0° is South and 90° is West.
For some applications the slope and azimuth angles will already be known, for instance if the PV modules are to be built into an existing roof. However, if you have the possibility to choose the inclination and/or orientation, this application can also calculate for you the optimal values for inclination and orientation (assuming fixed angles for the entire year).
-
optimalinclination (bool): Calculate the optimum inclination angle (1) or not (0). (Optional)
- Optimize slope
If you click to choose this option, PVGIS will calculate the slope of the PV modules that gives the highest energy output for the whole year. This assumes that the slope angle stays fixed for the entire year.
-
optimalangles (bool): Calculate the optimum inclination and orientation angles (1) or not (0). (Optional)
- Optimize inclination and azimuth
If you click to choose this option, PVGIS will calculate the inclination AND orientation/azimuth of the PV modules that gives the highest energy output for the whole year. This assumes that the mounting of the PV modules stays fixed for the entire year.
-
inclined_axis (bool): Calculate a single inclined axis system (1) or not (0). (Optional)
- Inclined Axis
In this type of PV system, the modules are mounted on a structure rotating around an axis that forms an angle with the ground and points in the north-south direction. The plane of the modules is assumed to be parallel to the axis of rotation. It is assumed that the axis rotates during the day such that the angle to the sun is always as small as possible (this means that it will not rotate at constant speed during the day). The angle of the axis relative to the ground can be given, or you can ask to calculate the optimal angle for your location.
-
inclinedaxisangle (float): Inclination angle for a single inclined axis system. (Optional)
- Inclination angle or slope
This is the angle of the PV modules from the horizontal plane, for a fixed (non-tracking) mounting.
For some applications the slope and orientation angles will already be known, for instance if the PV modules are to be built into an existing roof. However, if you have the possibility to choose the slope and/or azimuth (orientation), this application can also calculate for you the optimal values for slope and orientation (assuming fixed angles for the entire year).
-
inclined_optimum (bool): Calculate optimum angle for a single inclined axis system (1) or not (0). (Optional)
- Optimize inclination of inclined-axis mounting
If you click to choose this option, PVGIS will calculate the inclination of the inclined rotating axis that the PV modules are mounted on which gives the highest energy output for the whole year.
-
vertical_axis (bool): Calculate a single vertical axis system (1) or not (0). (Optional)
- Vertical Axis
In this type of PV system, the modules are mounted on a moving structure with a vertical rotating axis, at an angle. The structure rotates around the axis during the day such that the angle to the sun is always as small as possible (this means that it will not rotate at constant speed during the day). The angle of the modules relative to the ground can be given, or you can ask to calculate the optimal angle for your location.
-
verticalaxisangle (float): Inclination angle for a single vertical axis system. (Optional)
- Inclination angle or slope
This is the angle of the PV modules from the horizontal plane, for a fixed (non-tracking) mounting. For some applications the slope and orientation angles will already be known, for instance if the PV modules are to be built into an existing roof. However, if you have the possibility to choose the slope and/or azimuth (orientation), this application can also calculate for you the optimal values for slope and orientation (assuming fixed angles for the entire year).
-
vertical_optimum (bool): Calculate optimum angle for a single vertical axis system (1) or not (0). (Optional)
- Optimize slope
If you click to choose this option, PVGIS will calculate the slope of the PV modules that gives the highest energy output for the whole year. This assumes that the slope angle stays fixed for the entire year.
-
twoaxis (bool): Calculate a two-axis tracking system (1) or not (0). (Optional)
- Two Axis
In this type of PV system, the modules are mounted on a structure that can move the modules in the east-west direction and also tilt them at an angle from the ground, so that the modules always point at the sun. Note that the calculation still assumes that the modules do not concentrate the light directly from the sun, but can use all the light falling on the modules, both that coming directly from the sun and that coming from the rest of the sky.
-
pvprice (bool): Calculate the PV electricity price (1) or not (0). (Optional)
-
systemcost (float): Total cost of installing the PV system in your currency. (Required if pvprice=1)
- PV system cost
Here you should input the total cost of installing the PV system, including PV system components (PV modules, mounting, inverters, cables, etc.) and installation costs (planning, installation, ...). The choice of currency is up to you, the electricity price calculated by PVGIS will then be the price per kWh of electricity in the same currency you used.
-
interest (float): Interest in %/year. (Required if pvprice=1)
- Interest rate
This is the interest rate you pay on any loans needed to finance the PV system. This assumes a fixed interest rate on the loan which will be paid back in yearly instalments over the lifetime of the system.
-
lifetime (int): Expected lifetime of the PV system in years. (Required if pvprice=1)
- PV system lifetime
This is the expected lifetime of the PV system in years. This is used to calculate the effective electricity cost for the system. If the PV system happens to last longer the electricity cost will be correspondingly lower.
-
outputformat (str): Output format. Choices: "csv", "basic", "json". (Default: "csv")
- browser (bool): Setting browser=1 and accessing the service through a web browser will save the retrieved data to a file. (Default: 0)
Additional notes:
-
For the fixed PV system, if the parameter
optimalinclinationis set to1, the value defined for theangleparameter is ignored. Similarly, ifoptimalanglesis set to 1, values defined forangleandaspectare ignored. In which case the parameterptimalinclinationis not required either. -
For the inclined axis PV system analysis, the parameter
inclined_axismust be selected along with eitherinclinedaxisangleorinclined_optimum. -
If the parameter
inclined_optimumis selected, the inclination angle defined ininclinedaxisangleis ignored, so this parameter would not be necessary. -
Parameters regarding the vertical axis (
vertical_axis,vertical_optimumandverticalaxisangle) are related in the same way as the parameters used for the inclined axis PV system.
References
Cite the relevant literature, e.g. [1]_. You may also cite these references in the notes section above.
.. [1]
Examples:
Source code in pvgisprototype/cli/power/energy_.py
@app.command(
"grid",
no_args_is_help=True,
help=f":electric_plug: Estimate the energy production of a PV system connected to the electricity grid {NOT_IMPLEMENTED}",
)
def estimate_grid_connected_pv(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
peak_power: Annotated[
float,
typer.Argument(
rich_help_panel="Required",
help="The installed peak PV power in kWp",
min=0,
max=100000000,
),
], # peakpower
loss: Annotated[
float,
typer.Argument(
rich_help_panel="Preset", help="System losses in %", min=0, max=100
),
] = 14, # loss
solar_radiation_database: Annotated[
str | None,
typer.Argument(
rich_help_panel="Preset",
help="Solar radiation database with hourly time resolution",
),
] = "PVGIS-SARAH2", # raddatabase
consider_shadows: Annotated[
bool,
typer.Argument(
rich_help_panel="Preset", help="Calculate effect of horizon shadowing"
),
] = True, # usehorizon
horizon_heights: Annotated[List[float], typer_argument_horizon_heights] = None,
pv_techonology: Annotated[
str | None, typer_argument_pv_technology
] = None, # pvtechchoice
mounting_type: Annotated[str | None, typer_argument_mounting_type] = "free",
surface_orientation: Annotated[
float, typer_argument_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[float, typer_argument_surface_tilt] = SURFACE_TILT_DEFAULT,
optimise_surface_tilt: Annotated[
bool, typer_option_optimise_surface_tilt
] = OPTIMISE_SURFACE_TILT_FLAG_DEFAULT,
optimise_surface_geometry: Annotated[
bool, typer_option_optimise_surface_geometry
] = OPTIMISE_SURFACE_GEOMETRY_FLAG_DEFAULT,
single_axis_system: Annotated[
bool,
typer.Argument(
rich_help_panel="Optional",
help="Consider a single axis PV system -- Remove Me and improve single_axis_inclination!",
),
] = False, # inclined_axis
single_axis_inclination: Annotated[
float,
typer.Argument(
rich_help_panel="Optional",
help="Inclination for a single axis PV system",
min=0,
max=90,
),
] = 0, # inclinedaxisangle
optimise_single_axis_inclination: Annotated[
bool,
typer.Argument(
rich_help_panel="Optional",
help="Optimise inclination for a single axis PV system",
),
] = False, # inclined_optimum
vertical_axis_system: Annotated[
bool,
typer.Argument(
rich_help_panel="Optional",
help="Consider a single vertical axis PV system -- Remove Me and improve vertical_axis_inclination!",
),
] = False, # vertical_axis
vertical_axis_inclination: Annotated[
float,
typer.Argument(
rich_help_panel="Optional",
help="Inclination for a single axis PV system",
min=0,
max=90,
),
] = 0, # Verticalaxisangle
optimise_vertical_axis_inclination: Annotated[
bool,
typer.Argument(
rich_help_panel="Optional",
help="Optimise inclination for a single vertical axis PV system",
),
] = False, # vertical_optimum
two_axis_system: Annotated[
bool,
typer.Argument(
rich_help_panel="Optional",
help="Consider a two-axis tracking PV system -- Review Me!",
),
] = False, # twoaxis
electricity_price: Annotated[
bool,
typer.Argument(
rich_help_panel="Optional",
help="Calculate the PV electricity price (kwh/year) for the system cost in the user requested currency -- Review Me!",
),
] = False, # pvprice
cost: Annotated[
float,
typer.Argument(
rich_help_panel="Optional",
help="Total cost of installing the PV system [custom currency]",
min=0,
max=100000000,
),
] = 0, # systemcost
interest: Annotated[
float,
typer.Argument(
rich_help_panel="Optional", help="Interest in %/year", min=0, max=100
),
] = None, # interest
lifetime: Annotated[
int,
typer.Argument(
rich_help_panel="Optional",
help="Expected lifetime of the PV system in years",
min=0,
max=100,
),
] = 25, # lifetime
output_format: Annotated[
str | None, typer.Argument(help="Output format")
] = "csv", # outputformat
):
r"""Estimate the energy production of a PV system connected to the electricity grid
Performance of grid-connected PV systems. This tool makes it possible to
estimate the average monthly and yearly energy production of a PV system
connected to the electricity grid, without battery storage. The calculation
takes into account the solar radiation, temperature, wind speed and type of
PV module. The user can choose how the modules are mounted, whether on a
free-standing rack mounting, sun-tracking mountings or integrated in a
building surface. PVGIS can also calculate the optimum slope and
orientation that maximizes the yearly energy production.
Requires following data:
- elevation
- horizon
- dem_era5
- tgrad_bin
- sis
- sid
- temperature (ERA5 t2m)
- wind speed (ERA5 w2m)
- pv coefficients (current file: pvtech.coeffs)
- pv coefficients for bifacial? (current file: pvtech.coeffs_bipv)
- spectral correction data (current file: pvtech_spectraldata.bin)
Parameters
----------
longitude: float
latitude: float
peak_power: float
loss: float
solar_radiation_database: {'PVGIS-SARAH2', 'PVGIS-SARAH', 'PVGIS-NSRDB', 'PVGIS-ERA5'}, optional
consider_shadows: bool
horizon_heights: int
pv_techonology: str
mounting_type: str
inclination: float
orientation: float
optimise_inclination: bool
optimise_angles: bool
single_axis_system: bool
single_axis_inclination: float
optimise_single_axis_inclination: bool
vertical_axis_system: bool
vertical_axis_inclination: float
optimise_vertical_axis_inclination: bool
two_axis_system: bool
electricity_price: bool
cost: float
interest: float
lifetime: int
output_format: str
Other Parameters
----------------
only_seldom_used_keyword : int, optional
Infrequently used parameters can be described under this optional
section to prevent cluttering the Parameters section.
**kwargs : dict
Other infrequently used keyword arguments. Note that all keyword
arguments appearing after the first parameter specified under the
Other Parameters section, should also be described under this
section.
Returns
-------
str
PV calculation result based on the specified input parameters
Raises
------
BadException
Because you shouldn't have done that.
See Also
--------
estimate_energy.offgrid : Relationship (optional).
Notes
-----
The following notes are sourced from:
- the manual of PVcalc
- the web GUI
- the original C/C++ program `rsun_standalone_hourly_opt`
Original Parameters:
- lat (float): Latitude in decimal degrees, south is negative. (Required)
- lon (float): Longitude in decimal degrees, west is negative. (Required)
- Input latitude and longitude
Latitude and longitude can be input in the format DD:MM:SSA where
DD is the degrees, MM the arc-minutes, SS the arc-seconds and A the
hemisphere (N, S, E, W).
Latitude and longitude can also be input as decimal values, so for
instance 45°15'N should be input as 45.25. Latitudes south of the
equator are input as negative values, north are positive.
Longitudes west of the 0° meridian should be given as negative
values, eastern values are positive.
- peakpower (float): Nominal power of the PV system in kW. (Required)
- Installed peak PV power [kWp] - Peak power
This is the power that the manufacturer declares that the PV array
can produce under standard test conditions, which are a constant
1000W of solar irradiance per square meter in the plane of the
array, at an array temperature of 25°C. The peak power should be
entered in kilowatt-peak (kWp). If you do not know the declared
peak power of your modules but instead know the area of the modules
(in m2) and the declared conversion efficiency (in percent), you
can calculate the peak power as power (kWp) = 1 kW/m2 * area *
efficiency / 100. See more explanation in the FAQ
- loss (float): Sum of system losses in percent. (Required)
- Estimated system losses
The estimated system losses are all the losses in the system, which
cause the power actually delivered to the electricity grid to be
lower than the power produced by the PV modules. There are several
causes for this loss, such as losses in cables, power inverters,
dirt (sometimes snow) on the modules and so on. Over the years the
modules also tend to lose a bit of their power, so the average
yearly output over the lifetime of the system will be a few percent
lower than the output in the first years.
We have given a default value of 14% for the overall losses. If you
have a good idea that your value will be different (maybe due to a
really high-efficiency inverter) you may reduce this value a
little.
- raddatabase (str): Radiation database. (Default: "PVGIS-SARAH2")
- Solar radiation databases
- PVGIS offers four different solar radiation databases with hourly
time resolution. At the moment, there are three satellite-based
databases:
- PVGIS-SARAH2 (0.05º x 0.05º) Database produced by CM SAF to
replace SARAH-1 (PVGIS-SARAH). It covers Europe, Africa, most
of Asia, and parts of South America. Temporal range: 2005-2020.
- PVGIS-SARAH* (0.05º x 0.05º) Database produced using the CM SAF
algorithm. Similar coverage to SARAH-2. Temporal range:
2005-2016.
- PVGIS-NSRDB (0.04º x 0.04º) Result of a collaboration with NREL
(USA) under which the NSRDB solar radiation database was made
available for PVGIS. Temporal range: 2005-2015.
In addition to these, there is also a reanalysis database available
worldwide.
- PVGIS-ERA5 (0.25º x 0.25º) Latest global reanalysis of the
ECMWF (ECMWF). Temporal range: 2005-2020.
Reanalyses solar radiation data generally have larger uncertainty
than satellite-based databases. Therefore, we recommend using
reanalysis data only where satellite-based data are missing or
outdated. For more information about the databases and the
accuracy, see the PVGIS web page on the calculation methods.
- usehorizon (int): Consider shadows from high horizon (1) or not (0). (Default: 1)
- Calculated horizon
The solar radiation and PV output will change if there are local
hills or mountains that block the light of the sun during some
periods of the day. PVGIS can calculate the effect of this using
data about ground elevation with a resolution of 3 arc-seconds
(around 90m). This calculation does not take into account shadows
from very nearby objects such as houses or trees. In this case you
can upload your own horizon information.
It is normally a good idea to use the horizon shadowing option.
- userhorizon (list): Height of the horizon at equidistant directions
around the point of interest, in degrees. Starting at north and
moving clockwise. (Optional)
- User-defined horizon
PVGIS includes a database of the horizon height around each point
you can choose in the region. In this way, the calculation of PV
performance can take into account the effects of mountains and
hills casting shadows onto the PV system. The resolution of the
horizon information is 3 arc-seconds (around 90m), so objects that
are very near, such as houses or trees are not included. However,
you have the possibility to upload your own information about the
horizon height.
The horizon file to be uploaded to our web site should be a simple
text file, such as you can create using a text editor (such as
Notepad for Windows), or by exporting a spreadsheet as
comma-separated values (.csv). The file name must have the
extensions '.txt' or '.csv'.
In the file there should be one number per line, with each number
representing the horizon height in degrees in a certain compass
direction around the point of interest. The horizon height cannot
be higher than 90 degrees, so if the file contains a value higher
than that, it will be automatically replaced by 90.
The horizon heights in the file should be given in a clockwise
direction starting at North; that is, from North, going to East,
South, West, and back to North. The values are assumed to represent
equal angular distance around the horizon. For instance, if you
have 36 values in the file, PVGIS assumes that the first point is
due north, the next is 10 degrees east of north, and so on, until
the last point, 10 degrees west of north.
An example file can be found here. In this case, there are only 12
numbers in the file, corresponding to a horizon height for every 30
degrees around the horizon.
- pvtechchoice (str): PV technology choice. (Optional)
- PV technology
The performance of PV modules depends on the temperature and on the
solar irradiance, as well as on the spectrum of the sunlight, but
the exact dependence varies between different types of PV modules.
At the moment we can estimate the losses due to temperature and
irradiance effects for the following types of modules:
1. crystalline silicon cells
- thin film modules made from:
2. CIS or CIGS
3. Cadmium Telluride (CdTe)
For other technologies (especially various amorphous technologies),
this correction cannot be calculated here. If you choose one of the
first three options here the calculation of performance will take
into account the temperature dependence of the performance of the
chosen technology. If you choose the other option (other/unknown),
the calculation will assume a loss of 8% of power due to
temperature effects (a generic value which was found to be
reasonable for temperate climates). Note that the calculation of
the effect of spectral variations is at the moment only available
for crystalline silicon and for CdTe. The spectral effect cannot be
considered yet for the areas only covered by the PVGIS-NSRDB
database.
- mountingplace (str): Type of mounting of the PV modules. Choices are: "free" or "building". (Default: "free")
- fixed (bool): Calculate a fixed mounted system (1) or not (0). (Default: 1)
- Mounting position
For fixed (non-tracking) systems, the way the modules are mounted
will have an influence on the temperature of the module, which in
turn affects the efficiency. Experiments have shown that if the
movement of air behind the modules is restricted, the modules can
get considerably hotter (up to 15°C at 1000W/m2 of sunlight).
In the application there are two possibilities: free-standing,
meaning that the modules are mounted on a rack with air flowing
freely behind the modules; and roof added / building-integrated,
which means that the modules are completely built into the
structure of the wall or roof of a building, with little or no air
movement behind the modules.
Some types of mounting are in between these two extremes, for
instance if the modules are mounted on a roof with curved roof
tiles, allowing air to move behind the modules. In such cases, the
performance will be somewhere between the results of the two
calculations that are possible here. For such cases, to be
conservative, the roof added / building integrated option can be
used.
- angle (float): Inclination angle from horizontal plane of the fixed PV system. (Optional)
- Inclination angle or slope
This is the angle of the PV modules from the horizontal plane, for
a fixed (non-tracking) mounting.
For some applications the slope and orientation angles will already
be known, for instance if the PV modules are to be built into an
existing roof. However, if you have the possibility to choose the
slope and/or azimuth (orientation), this application can also
calculate for you the optimal values for slope and orientation
(assuming fixed angles for the entire year).
- aspect (float): Orientation (azimuth) angle of the fixed PV system. (Optional)
- Orientation angle or azimuth
The azimuth, or orientation, is the angle of the PV modules
relative to the direction due South. -90° is East, 0° is South and
90° is West.
For some applications the slope and azimuth angles will already be
known, for instance if the PV modules are to be built into an
existing roof. However, if you have the possibility to choose the
inclination and/or orientation, this application can also calculate
for you the optimal values for inclination and orientation
(assuming fixed angles for the entire year).
- optimalinclination (bool): Calculate the optimum inclination angle (1) or not (0). (Optional)
- Optimize slope
If you click to choose this option, PVGIS will calculate the slope
of the PV modules that gives the highest energy output for the
whole year. This assumes that the slope angle stays fixed for the
entire year.
- optimalangles (bool): Calculate the optimum inclination and orientation angles (1) or not (0). (Optional)
- Optimize inclination and azimuth
If you click to choose this option, PVGIS will calculate the
inclination AND orientation/azimuth of the PV modules that gives
the highest energy output for the whole year. This assumes that the
mounting of the PV modules stays fixed for the entire year.
- inclined_axis (bool): Calculate a single inclined axis system (1) or not (0). (Optional)
- Inclined Axis
In this type of PV system, the modules are mounted on a structure
rotating around an axis that forms an angle with the ground and
points in the north-south direction. The plane of the modules is
assumed to be parallel to the axis of rotation. It is assumed that
the axis rotates during the day such that the angle to the sun is
always as small as possible (this means that it will not rotate at
constant speed during the day). The angle of the axis relative to
the ground can be given, or you can ask to calculate the optimal
angle for your location.
- inclinedaxisangle (float): Inclination angle for a single inclined axis system. (Optional)
- Inclination angle or slope
This is the angle of the PV modules from the horizontal plane, for
a fixed (non-tracking) mounting.
For some applications the slope and orientation angles will already
be known, for instance if the PV modules are to be built into an
existing roof. However, if you have the possibility to choose the
slope and/or azimuth (orientation), this application can also
calculate for you the optimal values for slope and orientation
(assuming fixed angles for the entire year).
- inclined_optimum (bool): Calculate optimum angle for a single inclined axis system (1) or not (0). (Optional)
- Optimize inclination of inclined-axis mounting
If you click to choose this option, PVGIS will calculate the
inclination of the inclined rotating axis that the PV modules are
mounted on which gives the highest energy output for the whole
year.
- vertical_axis (bool): Calculate a single vertical axis system (1) or not (0). (Optional)
- Vertical Axis
In this type of PV system, the modules are mounted on a moving
structure with a vertical rotating axis, at an angle. The structure
rotates around the axis during the day such that the angle to the
sun is always as small as possible (this means that it will not
rotate at constant speed during the day). The angle of the modules
relative to the ground can be given, or you can ask to calculate
the optimal angle for your location.
- verticalaxisangle (float): Inclination angle for a single vertical axis system. (Optional)
- Inclination angle or slope
This is the angle of the PV modules from the horizontal plane, for
a fixed (non-tracking) mounting. For some applications the slope
and orientation angles will already be known, for instance if the
PV modules are to be built into an existing roof. However, if you
have the possibility to choose the slope and/or azimuth
(orientation), this application can also calculate for you the
optimal values for slope and orientation (assuming fixed angles for
the entire year).
- vertical_optimum (bool): Calculate optimum angle for a single vertical axis system (1) or not (0). (Optional)
- Optimize slope
If you click to choose this option, PVGIS will calculate the slope
of the PV modules that gives the highest energy output for the
whole year. This assumes that the slope angle stays fixed for the
entire year.
- twoaxis (bool): Calculate a two-axis tracking system (1) or not (0). (Optional)
- Two Axis
In this type of PV system, the modules are mounted on a structure
that can move the modules in the east-west direction and also tilt
them at an angle from the ground, so that the modules always point
at the sun. Note that the calculation still assumes that the
modules do not concentrate the light directly from the sun, but can
use all the light falling on the modules, both that coming directly
from the sun and that coming from the rest of the sky.
- pvprice (bool): Calculate the PV electricity price (1) or not (0). (Optional)
- systemcost (float): Total cost of installing the PV system in your currency. (Required if pvprice=1)
- PV system cost
Here you should input the total cost of installing the PV system,
including PV system components (PV modules, mounting, inverters,
cables, etc.) and installation costs (planning, installation, ...).
The choice of currency is up to you, the electricity price
calculated by PVGIS will then be the price per kWh of electricity
in the same currency you used.
- interest (float): Interest in %/year. (Required if pvprice=1)
- Interest rate
This is the interest rate you pay on any loans needed to finance
the PV system. This assumes a fixed interest rate on the loan which
will be paid back in yearly instalments over the lifetime of the
system.
- lifetime (int): Expected lifetime of the PV system in years. (Required if pvprice=1)
- PV system lifetime
This is the expected lifetime of the PV system in years. This is
used to calculate the effective electricity cost for the system. If
the PV system happens to last longer the electricity cost will be
correspondingly lower.
- outputformat (str): Output format. Choices: "csv", "basic", "json". (Default: "csv")
- browser (bool): Setting browser=1 and accessing the service through a
web browser will save the retrieved data to a file. (Default: 0)
Additional notes:
- For the fixed PV system, if the parameter `optimalinclination` is set to `1`,
the value defined for the `angle` parameter is ignored.
Similarly, if `optimalangles` is set to 1, values defined for `angle` and `aspect`
are ignored. In which case the parameter `ptimalinclination` is not required either.
- For the inclined axis PV system analysis,
the parameter `inclined_axis` must be selected along with
either `inclinedaxisangle` or `inclined_optimum`.
- If the parameter `inclined_optimum` is selected,
the inclination angle defined in `inclinedaxisangle` is ignored,
so this parameter would not be necessary.
- Parameters regarding the vertical axis
(`vertical_axis`, `vertical_optimum` and `verticalaxisangle`)
are related in the same way as the parameters used for the inclined axis PV system.
References
----------
Cite the relevant literature, e.g. [1]_. You may also cite these
references in the notes section above.
.. [1]
Examples
--------
"""
# Fake an output for now! -------------------------------------------------
path_to_module = os.path.dirname(__file__)
path_to_test_data = pathlib.Path(path_to_module).parent / "tests" / "data"
csv_files = path_to_test_data.glob("*.csv")
for csv_file in csv_files:
output_csv = csv_to_list_of_dictionaries(csv_file)
print(output_csv)
# ------------------------------------------------# Fake an output for now!
return 0
estimate_offgrid_pv ¶
Estimate the energy production of a PV system that is not connected to the electricity grid but instead relies on battery storage
Performance of off-grid PV systems. This part of PVGIS calculates the performance of PV systems that are not connected to the electricity grid but instead rely on battery storage to supply energy when the sun is not shining. The calculation uses information about the daily variation in electricity consumption for the system to simulate the flow of energy to the users and into and out of the battery.
Source code in pvgisprototype/cli/power/energy_.py
@app.command(
"offgrid",
no_args_is_help=True,
help=f":battery: Estimate PV power output for a system not connected to the grid {NOT_IMPLEMENTED}",
)
def estimate_offgrid_pv():
"""Estimate the energy production of a PV system that is not connected to
the electricity grid but instead relies on battery storage
Performance of off-grid PV systems. This part of PVGIS calculates the
performance of PV systems that are not connected to the electricity grid
but instead rely on battery storage to supply energy when the sun is not
shining. The calculation uses information about the daily variation in
electricity consumption for the system to simulate the flow of energy to
the users and into and out of the battery.
"""
print(f"{NOT_IMPLEMENTED}")
estimate_tracking_pv ¶
Estimate the energy production of a tracking PV system connected to the electricity grid
Performance of tracking PV. PV modules can be placed on mountings that move the PV modules to allow them to follow (track) the movement of the sun in the sky. In this way we can increase the amount of sunlight arriving at the PV modules. This movement can be made in several different ways. Here we give three options:
-
Vertical axis: The modules are mounted on a moving structure with a vertical rotating axis, at an angle. The structure rotates around the axis during the day such that the angle to the sun is always as small as possible (this means that it will not rotate at constant speed during the day). The angle of the modules relative to the ground can be given, or you can ask to calculate the optimal angle for your location.
-
Inclined axis: The modules are mounted on an structure rotating around an axis that forms an angle with the ground and points in the north-south direction. The plane of the modules is assumed to be parallel to the axis of rotation. It is assumed that the axis rotates during the day such that the angle to the sun is always as small as possible (this means that it will not rotate at constant speed during the day). The angle of the axis relative to the ground can be given, or you can ask to calculate the optimal angle for your location.
-
Two-axis tracker: The modules are mounted on a system that can move the modules in the east-west direction and also tilt them at an angle from the ground, so that the modules always point at the sun. Note that the calculation still assumes that the modules do not concentrate the light directly from the sun, but can use all the light falling on the modules, both that coming directly from the sun and that coming from the rest of the sky.
Source code in pvgisprototype/cli/power/energy_.py
@app.command(
"tracking",
no_args_is_help=True,
help=f":satellite_antenna: Estimate PV power output for a system not connected to the grid {NOT_IMPLEMENTED}",
)
def estimate_tracking_pv():
"""Estimate the energy production of a tracking PV system connected to the electricity grid
Performance of tracking PV. PV modules can be placed on mountings that
move the PV modules to allow them to follow (track) the movement of the sun
in the sky. In this way we can increase the amount of sunlight arriving at
the PV modules. This movement can be made in several different ways. Here
we give three options:
- Vertical axis: The modules are mounted on a moving structure with a
vertical rotating axis, at an angle. The structure rotates around the
axis during the day such that the angle to the sun is always as small as
possible (this means that it will not rotate at constant speed during the
day). The angle of the modules relative to the ground can be given, or
you can ask to calculate the optimal angle for your location.
- Inclined axis: The modules are mounted on an structure rotating around an
axis that forms an angle with the ground and points in the north-south
direction. The plane of the modules is assumed to be parallel to the axis
of rotation. It is assumed that the axis rotates during the day such that
the angle to the sun is always as small as possible (this means that it
will not rotate at constant speed during the day). The angle of the axis
relative to the ground can be given, or you can ask to calculate the
optimal angle for your location.
- Two-axis tracker: The modules are mounted on a system that can move the
modules in the east-west direction and also tilt them at an angle from
the ground, so that the modules always point at the sun. Note that the
calculation still assumes that the modules do not concentrate the light
directly from the sun, but can use all the light falling on the modules,
both that coming directly from the sun and that coming from the rest of
the sky.
"""
print(f"{NOT_IMPLEMENTED}")
introduction ¶
Functions:
| Name | Description |
|---|---|
photovoltaic_power_introduction | A short introduction on photovoltaic performance |
photovoltaic_power_introduction ¶
A short introduction on photovoltaic performance
Source code in pvgisprototype/cli/power/introduction.py
def photovoltaic_power_introduction():
"""A short introduction on photovoltaic performance"""
introduction = """
[underline]Photovoltaic power[/underline] is the [blue]electrical
power[/blue] generated by a photovoltaic (PV) system when solar irradiance
strikes its surface and is converted into electricity.
This process relies on the [italic]photovoltaic effect[/italic], where
solar cells convert sunlight into direct current (DC) electricity, which
can then be used, stored, or fed into the electrical grid. The amount of
power produced by a PV system depends on various factors such as the
[italic]angle of sunlight[/italic], the [italic]efficiency[/italic] of the
solar panels, environmental conditions, and the available solar irradiance.
"""
note = """
PVGIS can estimate the photovoltaic power output for a series of
technologies using either [magenta]broadband[/magenta] or
[magenta]spectrally resolved[/magenta] irradiance data.
"""
from rich.panel import Panel
note_in_a_panel = Panel(
"[italic]{}[/italic]".format(note),
title="[bold cyan]Note[/bold cyan]",
width=78,
)
from rich.console import Console
console = Console()
# introduction.wrap(console, 30)
console.print(introduction)
console.print(note_in_a_panel)
console.print(A_PRIMER_ON_PHOTOVOLTAIC_PERFORMANCE)
power ¶
Functions:
| Name | Description |
|---|---|
calculate_peak_power | Calculate the peak power in kW based on area and conversion efficiency |
calculate_peak_power ¶
calculate_peak_power(
area: Annotated[float, typer_argument_area],
conversion_efficiency: Annotated[
float, typer_argument_conversion_efficiency
],
)
Calculate the peak power in kW based on area and conversion efficiency
.. math:: Power = 1/m^{2} * area * efficiency / 100
Returns: Power in kWp
Source code in pvgisprototype/cli/power/power.py
@app.command(
"peak-power",
no_args_is_help=True,
help=f"{PEAK_POWER_UNIT} Calculate the peak power in kW based on area and conversion efficiency {NOT_IMPLEMENTED_CLI}",
rich_help_panel=rich_help_panel_power_toolbox,
)
def calculate_peak_power(
area: Annotated[float, typer_argument_area],
conversion_efficiency: Annotated[float, typer_argument_conversion_efficiency],
):
"""Calculate the peak power in kW based on area and conversion efficiency
.. math:: Power = 1/m^{2} * area * efficiency / 100
Returns:
Power in kWp
"""
power = 1 / area * conversion_efficiency / 100
return power
spectral ¶
Functions:
| Name | Description |
|---|---|
spectral_photovoltaic_power_output_series | This method accounts for the effects of the solar spectrum's varying |
spectral_photovoltaic_power_output_series ¶
spectral_photovoltaic_power_output_series(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None, typer_option_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_option_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
str | None, typer_option_timezone
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
spectrally_resolved_global_horizontal_irradiance_series: Annotated[
Path | None,
typer_option_global_horizontal_irradiance,
] = None,
spectrally_resolved_direct_horizontal_irradiance_series: Annotated[
Path | None,
typer_option_direct_horizontal_irradiance,
] = None,
number_of_junctions: int = 1,
spectral_response_data: Path | None = None,
standard_conditions_response: Path | None = None,
minimum_spectral_mismatch=MINIMUM_SPECTRAL_MISMATCH,
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor,
typer_option_linke_turbidity_factor_series,
] = None,
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[
float | None, typer_option_albedo
] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = True,
solar_position_model: Annotated[
SolarPositionModel,
typer_option_solar_position_model,
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel,
typer_option_solar_incidence_model,
] = iqbal,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[
float, typer_option_solar_constant
] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[
float, typer_option_eccentricity_phase_offset
] = value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = value,
time_output_units: Annotated[
str, typer_option_time_output_units
] = MINUTES,
angle_units: Annotated[
str, typer_option_angle_units
] = RADIANS,
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = RADIANS,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel,
typer_option_pv_power_algorithm,
] = king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm,
typer_option_module_temperature_algorithm,
] = faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
)
This method accounts for the effects of the solar spectrum's varying wavelengths on PV output, offering a more detailed analysis for systems sensitive to specific spectral ranges.
Source code in pvgisprototype/cli/power/spectral.py
def spectral_photovoltaic_power_output_series(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
elevation: Annotated[float, typer_argument_elevation],
surface_orientation: Annotated[
float | None, typer_option_surface_orientation
] = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: Annotated[
float | None, typer_option_surface_tilt
] = SURFACE_TILT_DEFAULT,
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[datetime | None, typer_option_start_time] = None,
periods: Annotated[int | None, typer_option_periods] = None,
frequency: Annotated[str | None, typer_option_frequency] = None,
end_time: Annotated[datetime | None, typer_option_end_time] = None,
timezone: Annotated[str | None, typer_option_timezone] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
spectrally_resolved_global_horizontal_irradiance_series: Annotated[
Path | None, typer_option_global_horizontal_irradiance
] = None,
spectrally_resolved_direct_horizontal_irradiance_series: Annotated[
Path | None, typer_option_direct_horizontal_irradiance
] = None,
number_of_junctions: int = 1,
spectral_response_data: Path | None = None,
standard_conditions_response: Path | None = None, #: float = 1, # STCresponse : read from external data
# extraterrestrial_normal_irradiance_series, # spectral_ext,
minimum_spectral_mismatch=MINIMUM_SPECTRAL_MISMATCH,
temperature_series: Annotated[
TemperatureSeries, typer_argument_temperature_series
] = TEMPERATURE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_argument_wind_speed_series
] = WIND_SPEED_DEFAULT,
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
# dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
# array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
# multi_thread: Annotated[bool, typer_option_multi_thread] = MULTI_THREAD_FLAG_DEFAULT,
linke_turbidity_factor_series: Annotated[
LinkeTurbidityFactor, typer_option_linke_turbidity_factor_series
] = None, # Changed this to np.ndarray
adjust_for_atmospheric_refraction: Annotated[
bool, typer_option_adjust_for_atmospheric_refraction
] = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: Annotated[
float | None, typer_option_refracted_solar_zenith
] = UNREFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
albedo: Annotated[float | None, typer_option_albedo] = ALBEDO_DEFAULT,
apply_reflectivity_factor: Annotated[
bool, typer_option_apply_reflectivity_factor
] = True,
solar_position_model: Annotated[
SolarPositionModel, typer_option_solar_position_model
] = SOLAR_POSITION_ALGORITHM_DEFAULT,
solar_incidence_model: Annotated[
SolarIncidenceModel, typer_option_solar_incidence_model
] = SolarIncidenceModel.iqbal,
solar_time_model: Annotated[
SolarTimeModel, typer_option_solar_time_model
] = SOLAR_TIME_ALGORITHM_DEFAULT,
solar_constant: Annotated[float, typer_option_solar_constant] = SOLAR_CONSTANT,
eccentricity_phase_offset: Annotated[float, typer_option_eccentricity_phase_offset] = EccentricityPhaseOffset().value,
eccentricity_amplitude: Annotated[
float, typer_option_eccentricity_amplitude
] = EccentricityAmplitude().value,
time_output_units: Annotated[str, typer_option_time_output_units] = MINUTES,
angle_units: Annotated[str, typer_option_angle_units] = RADIANS,
angle_output_units: Annotated[str, typer_option_angle_output_units] = RADIANS,
# horizon_heights: Annotated[List[float], typer.Argument(help="Array of horizon elevations.")] = None,
system_efficiency: Annotated[
float | None, typer_option_system_efficiency
] = SYSTEM_EFFICIENCY_DEFAULT,
power_model: Annotated[
PhotovoltaicModulePerformanceModel, typer_option_pv_power_algorithm
] = PhotovoltaicModulePerformanceModel.king,
temperature_model: Annotated[
ModuleTemperatureAlgorithm, typer_option_module_temperature_algorithm
] = ModuleTemperatureAlgorithm.faiman,
efficiency: Annotated[
float | None, typer_option_efficiency
] = EFFICIENCY_FACTOR_DEFAULT,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
):
"""
This method accounts for the effects of the solar spectrum's varying
wavelengths on PV output, offering a more detailed analysis for systems
sensitive to specific spectral ranges.
"""
(
spectrally_resolved_photovoltaic_power,
results,
title,
) = calculate_spectral_photovoltaic_power_output(
longitude=longitude,
latitude=latitude,
elevation=elevation,
timestamps=timestamps,
timezone=timezone,
spectrally_resolved_global_horizontal_irradiance_series=spectrally_resolved_global_horizontal_irradiance_series,
spectrally_resolved_direct_horizontal_irradiance_series=spectrally_resolved_direct_horizontal_irradiance_series,
spectral_response_data=spectral_response_data,
number_of_junctions=number_of_junctions,
standard_conditions_response=standard_conditions_response,
minimum_spectral_mismatch=minimum_spectral_mismatch,
temperature_series=temperature_series,
wind_speed_series=wind_speed_series,
mask_and_scale=mask_and_scale,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
in_memory=in_memory,
surface_orientation=surface_orientation,
surface_tilt=surface_tilt,
linke_turbidity_factor_series=linke_turbidity_factor_series,
adjust_for_atmospheric_refraction=adjust_for_atmospheric_refraction,
unrefracted_solar_zenith=unrefracted_solar_zenith,
albedo=albedo,
apply_reflectivity_factor=apply_reflectivity_factor,
solar_position_model=solar_position_model,
solar_incidence_model=solar_incidence_model,
solar_time_model=solar_time_model,
solar_constant=solar_constant,
eccentricity_phase_offset=eccentricity_phase_offset,
eccentricity_amplitude=eccentricity_amplitude,
time_output_units=time_output_units,
angle_units=angle_units,
angle_output_units=angle_output_units,
system_efficiency=system_efficiency,
power_model=power_model,
temperature_model=temperature_model,
efficiency=efficiency,
verbose=verbose,
)
# longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
# latitude = convert_float_to_degrees_if_requested(latitude, angle_output_units)
if not quiet:
if verbose > 0:
pass
# print_irradiance_table_2(
# longitude=longitude,
# latitude=latitude,
# timestamps=timestamps,
# dictionary=results,
# title=title + f' irradiance series {IRRADIANCE_UNIT}',
# rounding_places=rounding_places,
# index=index,
# verbose=verbose,
# )
else:
flat_list = spectrally_resolved_photovoltaic_power.flatten().astype(str)
csv_str = ",".join(flat_list)
print(csv_str)
temperature ¶
Functions:
| Name | Description |
|---|---|
photovoltaic_module_temperature | |
photovoltaic_module_temperature ¶
photovoltaic_module_temperature(
irradiance_series: Annotated[
IrradianceSeries, typer_argument_irradiance_series
],
temperature_series: Annotated[
TemperatureSeries, typer_option_temperature_series
] = TEMPERATURE_DEFAULT,
photovoltaic_module: Annotated[
PhotovoltaicModuleModel,
typer_option_photovoltaic_module_model,
] = PHOTOVOLTAIC_MODULE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_option_wind_speed_series
] = WIND_SPEED_DEFAULT,
temperature_model: Annotated[
ModuleTemperatureAlgorithm,
typer_option_module_temperature_algorithm,
] = faiman,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[
bool, typer_option_command_metadata
] = False,
)
Source code in pvgisprototype/cli/power/temperature.py
@log_function_call
def photovoltaic_module_temperature(
irradiance_series: Annotated[
IrradianceSeries, typer_argument_irradiance_series
],
temperature_series: Annotated[
TemperatureSeries, typer_option_temperature_series
] = TEMPERATURE_DEFAULT,
photovoltaic_module: Annotated[
PhotovoltaicModuleModel, typer_option_photovoltaic_module_model
] = PHOTOVOLTAIC_MODULE_DEFAULT,
wind_speed_series: Annotated[
WindSpeedSeries, typer_option_wind_speed_series
] = WIND_SPEED_DEFAULT,
temperature_model: Annotated[
ModuleTemperatureAlgorithm, typer_option_module_temperature_algorithm
] = ModuleTemperatureAlgorithm.faiman,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
metadata: Annotated[bool, typer_option_command_metadata] = False,
):
"""
"""
temperature_effect = calculate_photovoltaic_module_temperature_series(
irradiance_series=irradiance_series,
temperature_series=temperature_series,
photovoltaic_module=photovoltaic_module,
wind_speed_series=wind_speed_series,
temperature_model=temperature_model,
verbose=verbose,
)
return temperature_effect
print ¶
Modules:
caption ¶
Functions:
| Name | Description |
|---|---|
build_caption | Build the main caption for a solar position tabular output. |
build_simple_caption | Notes |
build_caption ¶
build_caption(
data_dictionary,
longitude,
latitude,
elevation=None,
rounding_places: int = ROUNDING_PLACES_DEFAULT,
surface_orientation=True,
surface_tilt=True,
minimum_value=None,
maximum_value=None,
)
Build the main caption for a solar position tabular output.
Include :
-
Location
- Longitude ϑ
- Latitude ϕ
- Elevation + Unit
-
Position
- Surface Orientation ↻
- Surface Tilt ⦥
- Angular units
Notes
Add the surface orientation and tilt only if they exist in the input data_dictionary !
Source code in pvgisprototype/cli/print/caption.py
def build_caption(
data_dictionary,
longitude,
latitude,
elevation=None,
rounding_places: int = ROUNDING_PLACES_DEFAULT,
surface_orientation=True,
surface_tilt=True,
minimum_value=None,
maximum_value=None,
):
"""Build the _main_ caption for a solar position tabular output.
Include :
- Location
- Longitude ϑ
- Latitude ϕ
- Elevation + Unit
- Position
- Surface Orientation ↻
- Surface Tilt ⦥
- Angular units
Notes
-----
Add the surface orientation and tilt only if they exist in the input
`data_dictionary` !
"""
# Collect items from `data_dictionary`
first_model = next(iter(data_dictionary))
data_dictionary = flatten_dictionary(data_dictionary[first_model])
surface_orientation = round_float_values(
(
data_dictionary.get(SURFACE_ORIENTATION_COLUMN_NAME, None)
if surface_orientation
else ""
),
rounding_places,
)
surface_tilt = round_float_values(
(
data_dictionary.get(SURFACE_TILT_COLUMN_NAME, None)
if surface_tilt
else None
),
rounding_places,
)
angular_units = data_dictionary.get(ANGLE_UNITS_COLUMN_NAME, UNITLESS)
# Build caption
caption = str()
# Location
if longitude or latitude or elevation:
caption += "[underline]Location[/underline] "
## Longitude ϑ
## Latitude ϕ
if longitude and latitude:
caption += f"{LONGITUDE_COLUMN_NAME}, {LATITUDE_COLUMN_NAME} = [bold]{longitude}[/bold], [bold]{latitude}[/bold]"
## Angular units
if (
longitude
or latitude
or elevation
or surface_orientation
# or rear_side_surface_orientation
or surface_tilt
# or rear_side_surface_tilt
and angular_units is not None
):
caption += f" [underline]Angular units[/underline] [dim][code]{angular_units}[/code][/dim]"
## Elevation + Unit
if elevation:
caption += f"{ELEVATION_COLUMN_NAME}: [bold]{elevation}[/bold]\n"
# Position
if (
surface_orientation
# or rear_side_surface_orientation
or surface_tilt
# or rear_side_surface_tilt
and angular_units is not None
):
caption += f"\n[underline]Position[/underline] "
## Surface Orientation ↻
if surface_orientation is not None:
caption += (
f"{SURFACE_ORIENTATION_COLUMN_NAME}: [bold]{surface_orientation}[/bold], "
)
## Surface Tilt ⦥
if surface_tilt is not None:
caption += f"{SURFACE_TILT_COLUMN_NAME}: [bold]{surface_tilt}[/bold] "
# What is this required for ? --------------------------------------------
if minimum_value:
caption += f"Minimum : {minimum_value}"
if maximum_value:
caption += f"Minimum : {maximum_value}"
return caption
build_simple_caption ¶
build_simple_caption(
longitude,
latitude,
rounded_table,
timezone,
user_requested_timezone,
minimum_value=None,
maximum_value=None,
)
Notes
Add the surface orientation and tilt only if they exist in the input rounded_table !
Source code in pvgisprototype/cli/print/caption.py
def build_simple_caption(
longitude,
latitude,
rounded_table,
timezone,
user_requested_timezone,
minimum_value=None,
maximum_value=None,
):
"""
Notes
-----
Add the surface orientation and tilt only if they exist in the input
`rounded_table` !
"""
caption = (
f"[underline]Position[/underline] "
+ (
f"Orientation : [bold blue]{rounded_table.get(SURFACE_ORIENTATION_NAME).value}[/bold blue], "
if rounded_table.get(SURFACE_ORIENTATION_NAME) is not None
else ""
)
+ (
f"Tilt : [bold blue]{rounded_table.get(SURFACE_TILT_NAME).value}[/bold blue] "
if rounded_table.get(SURFACE_TILT_NAME) is not None
else ""
)
+ f"\n[underline]Location[/underline] "
+ (
f"{LONGITUDE_COLUMN_NAME}, {LATITUDE_COLUMN_NAME} = [bold]{longitude}[/bold], [bold]{latitude}[/bold], "
)
+ f"[dim]{rounded_table.get(UNIT_NAME, UNITLESS)}[/dim]"
f"\n[underline]{MEAN_PHOTOVOLTAIC_POWER_NAME}[/underline] "
+ f"[bold blue]{rounded_table.get(MEAN_PHOTOVOLTAIC_POWER_NAME)}[/bold blue] "
f"\n[underline]Algorithms[/underline] " # ---------------------------
f"Timing : [bold]{rounded_table.get(TIME_ALGORITHM_NAME, NOT_AVAILABLE)}[/bold], "
)
if user_requested_timezone is not None and user_requested_timezone != ZoneInfo(
"UTC"
):
caption += f"Local Zone : [bold]{user_requested_timezone}[/bold], "
else:
caption += f"Zone : [bold]{timezone}[/bold], "
if minimum_value:
caption += f"Minimum : {minimum_value}"
if maximum_value:
caption += f"Minimum : {maximum_value}"
return caption
citation ¶
Functions:
| Name | Description |
|---|---|
print_citation_text | |
fingerprint ¶
Functions:
| Name | Description |
|---|---|
build_fingerprint_panel | |
print_finger_hash | Print the fingerprint if found, otherwise print a warning. |
retrieve_fingerprint | Recursively search for the fingerprint key in a nested dictionary. |
build_fingerprint_panel ¶
Source code in pvgisprototype/cli/print/fingerprint.py
print_finger_hash ¶
Print the fingerprint if found, otherwise print a warning.
Source code in pvgisprototype/cli/print/fingerprint.py
def print_finger_hash(
dictionary: dict,
fingerprint_key: str = FINGERPRINT_COLUMN_NAME,
):
"""Print the fingerprint if found, otherwise print a warning."""
fingerprint = retrieve_fingerprint(
dictionary,
fingerprint_key,
)
if fingerprint is None:
fingerprint = "No fingerprint found!"
color = "red"
else:
color = "yellow"
fingerprint_panel = Panel.fit(
Text(f"{fingerprint}", justify="center", style=f"bold {color}"),
subtitle="[reverse]Fingerprint[/reverse]",
subtitle_align="right",
border_style="dim",
style="dim",
)
Console().print(fingerprint_panel)
retrieve_fingerprint ¶
retrieve_fingerprint(
dictionary: dict,
fingerprint_key: str = FINGERPRINT_COLUMN_NAME,
) -> str | None
Recursively search for the fingerprint key in a nested dictionary.
Source code in pvgisprototype/cli/print/fingerprint.py
def retrieve_fingerprint(
dictionary: dict, fingerprint_key: str = FINGERPRINT_COLUMN_NAME
) -> str | None:
"""
Recursively search for the fingerprint key in a nested dictionary.
"""
if isinstance(dictionary, dict):
if fingerprint_key in dictionary:
logger.info(f"Found the fingerprint key {fingerprint_key=}")
return dictionary[fingerprint_key]
# Recursively search each value of the dictionary
for _, value in dictionary.items():
fingerprint = retrieve_fingerprint(value)
if fingerprint is not None:
logger.info(f"Retrieved the fingerprint {fingerprint=}")
return fingerprint
logger.debug(f"Did not identify a fingerprint in the input data structure {dictionary=} !")
return None
flat ¶
Functions:
| Name | Description |
|---|---|
flatten_dictionary | Flatten a nested dictionary |
flatten_dictionary ¶
Flatten a nested dictionary
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dictionary | The nested dictionary to flatten | required |
Returns:
| Type | Description |
|---|---|
A flattened dictionary excluding the specified keys | |
Source code in pvgisprototype/cli/print/flat.py
def flatten_dictionary(dictionary):
"""
Flatten a nested dictionary
Parameters
----------
dictionary: dict
The nested dictionary to flatten
Returns
-------
A flattened dictionary excluding the specified keys
"""
flat_dictionary = {}
def flatten(input_dictionary):
for key, value in input_dictionary.items():
if isinstance(value, dict):
flatten(value)
else:
# Discard empty arrays
if isinstance(value, ndarray):
if value.size == 0:
continue
# Discard arrays that are all NaN
elif (
issubclass(value.dtype.type, (float, int))
and isnan(value).all()
):
continue
else:
flat_dictionary[key] = value
else:
flat_dictionary[key] = value
flatten(dictionary)
return flat_dictionary
getters ¶
Functions:
| Name | Description |
|---|---|
get_event_time_value | Safely get the event time |
get_scalar | Safely get a scalar value from an array or return the value itself |
get_value_or_default | Get a value from a flat or nested dict using a string key or Enum. |
get_event_time_value ¶
Safely get the event time
Source code in pvgisprototype/cli/print/getters.py
def get_event_time_value(
dictionary,
idx,
rounding_places,
):
"""Safely get the event time """
if dictionary is not None:
event_time_series = get_value_or_default(
dictionary=dictionary,
key=SolarPositionParameter.event_time,
default=None,
)
if event_time_series is not None and not (
isinstance(event_time_series, DatetimeIndex) and
all(isna(x) for x in event_time_series)
):
return get_scalar(event_time_series, idx, rounding_places)
else:
return None
get_scalar ¶
Safely get a scalar value from an array or return the value itself
get_value_or_default ¶
Get a value from a flat or nested dict using a string key or Enum.
Source code in pvgisprototype/cli/print/getters.py
def get_value_or_default(
dictionary: dict,
key: str | Enum,
default: str | None = None,
):
"""Get a value from a flat or nested dict using a string key or Enum."""
# if dictionary is not None:
# return dictionary.get(key, default)
# else:
# return None
from enum import Enum
if dictionary is None:
return None
# If key is an Enum, use its value
if isinstance(key, Enum):
key = key.value
# 1) Try top level (flat dict)
if key in dictionary:
return dictionary[key]
# 2) Try the 'Core' section (new nested structure)
core = dictionary.get("Core")
if isinstance(core, dict) and key in core:
return core[key]
# 3) Not found: return default
return default
helpers ¶
Functions:
| Name | Description |
|---|---|
determine_frequency | |
infer_frequency_from_timestamps | Process timestamps to infer frequency based on regularity or irregularity of intervals. |
determine_frequency ¶
Source code in pvgisprototype/cli/print/helpers.py
def determine_frequency(timestamps):
""" """
# single timestamp ?
if len(timestamps) == 1:
return "Single", "Single Timestamp"
time_groupings = {
"YE": "Yearly",
"S": "Seasonal",
"ME": "Monthly",
"W": "Weekly",
"D": "Daily",
"3h": "3-Hourly",
"h": "Hourly",
"min": "Minutely",
"8min": "8-Minutely",
}
if timestamps.year.unique().size > 1:
frequency = "YE"
elif timestamps.month.unique().size > 1:
frequency = "ME"
elif timestamps.to_period('W').week.unique().size > 1:
frequency = "W"
elif timestamps.day.unique().size > 1:
frequency = "D"
elif timestamps.hour.unique().size > 1:
if timestamps.hour.unique().size < 17: # Explain Me !
frequency = "h"
else:
frequency = "3h"
elif timestamps.minute.unique().size < 17: # Explain Me !
frequency = "min"
else:
frequency = "8min" # by 8 characters for a sparkline if timestamps > 64 min
frequency_label = time_groupings[frequency]
return frequency, frequency_label
infer_frequency_from_timestamps ¶
Process timestamps to infer frequency based on regularity or irregularity of intervals.
Source code in pvgisprototype/cli/print/helpers.py
def infer_frequency_from_timestamps(timestamps: DatetimeIndex):
"""
Process timestamps to infer frequency based on regularity or irregularity of intervals.
"""
if timestamps.freqstr: # timestamps are regular
logger.debug(
f"Regular intervals detected: {timestamps.freqstr}",
alt=f"[bold]Regular intervals detected:[/bold] {timestamps.freqstr}",
)
return timestamps.freqstr, f"{timestamps.freqstr}"
else:
try:
# Calculate time differences directly with NumPy for regular intervals
time_deltas = numpy.diff(timestamps).astype("timedelta64[ns]")
# Find the most frequent time delta using numpy.unique
unique_deltas, counts = numpy.unique(time_deltas, return_counts=True)
frequency = unique_deltas[numpy.argmax(counts)]
logger.debug(
f"Inferred frequency of timestamps: {frequency}",
alt=f"[bold]Inferred frequency of timestamps:[/bold] {frequency}",
)
# Calculate the total duration : end - start
total_duration = (timestamps[-1] - timestamps[0]).astype("timedelta64[ns]")
# Calculate the number of intervals
intervals = total_duration / frequency
# Check if the number of intervals matches the number of timestamps - 1 (with tolerance)
if numpy.isclose(len(timestamps) - 1, intervals, atol=1e-8):
# If the intervals match, we can say the series is regular
from pandas import to_timedelta
return frequency, f"Regular intervals of {to_timedelta(frequency)}"
else:
try:
# Fallback to determine_frequency for irregular intervals
frequency, frequency_label = determine_frequency(timestamps)
logger.debug(
f"Categorized irregular frequency: {frequency_label}",
alt=f"[bold]Categorized irregular frequency:[/bold] {frequency_label}",
)
return frequency, frequency_label
except Exception as e:
logger.error(f"Error in irregular frequency determination: {e}")
return None, "Error in determining irregular frequency"
except Exception as e:
logger.error(f"Error in regular frequency determination: {e}")
return None, "Error in determining regular frequency"
hour_angle ¶
Functions:
| Name | Description |
|---|---|
print_hour_angle_table | |
print_hour_angle_table ¶
print_hour_angle_table(
latitude,
rounding_places,
surface_tilt=None,
declination=None,
hour_angle=None,
units=None,
) -> None
Source code in pvgisprototype/cli/print/hour_angle.py
def print_hour_angle_table(
latitude,
rounding_places,
surface_tilt=None,
declination=None,
hour_angle=None,
units=None,
) -> None:
""" """
latitude = round_float_values(latitude, rounding_places)
# rounded_table = round_float_values(table, rounding_places)
surface_tilt = round_float_values(surface_tilt, rounding_places)
declination = round_float_values(declination, rounding_places)
hour_angle = round_float_values(hour_angle, rounding_places)
columns = ["Latitude", "Event"]
if surface_tilt is not None:
columns.append(SURFACE_TILT_COLUMN_NAME)
if declination is not None:
columns.append(DECLINATION_COLUMN_NAME)
if hour_angle is not None:
columns.append(HOUR_ANGLE_COLUMN_NAME)
columns.append(UNITS_COLUMN_NAME)
table = Table(
*columns,
box=SIMPLE_HEAD,
show_header=True,
header_style="bold magenta",
)
row = [str(latitude), "Event"]
if surface_tilt is not None:
row.append(str(surface_tilt))
if declination is not None:
row.append(str(declination))
if hour_angle is not None:
row.append(str(hour_angle))
row.append(str(units))
table.add_row(*row)
Console().print(table)
irradiance ¶
Modules:
| Name | Description |
|---|---|
caption | |
columns | |
data | |
table | |
text | |
caption ¶
Functions:
| Name | Description |
|---|---|
build_caption_for_irradiance_data | |
build_caption_for_irradiance_data ¶
build_caption_for_irradiance_data(
longitude=None,
latitude=None,
elevation=None,
timezone: ZoneInfo | None = None,
dictionary: dict = dict(),
rear_side_irradiance_data: dict = dict(),
rounding_places: int = ROUNDING_PLACES_DEFAULT,
)
Source code in pvgisprototype/cli/print/irradiance/caption.py
def build_caption_for_irradiance_data(
longitude=None,
latitude=None,
elevation=None,
timezone: ZoneInfo | None = None,
dictionary: dict = dict(),
rear_side_irradiance_data: dict = dict(),
rounding_places: int = ROUNDING_PLACES_DEFAULT,
):
"""
"""
caption = str()
# Collect ----------------------------------------------------------------
surface_orientation = round_float_values(
dictionary.get(SolarSurfacePositionParameterColumnName.orientation, None),
rounding_places,
)
surface_tilt = round_float_values(
dictionary.get(SolarSurfacePositionParameterColumnName.tilt, None),
rounding_places,
)
# Multiple solar surfaces ?
surface_orientations = dictionary.get(
SolarSurfacePositionParameterColumnName.orientations, None
)
surface_tilts = dictionary.get(SolarSurfacePositionParameterColumnName.tilts, None)
# Units for both front-side and rear-side too ! Should _be_ the same !
angular_units = dictionary.get(ANGLE_UNITS_COLUMN_NAME, UNITLESS)
# Mainly about : Mono- or Bi-Facial ?
# Maybe do the following :
# If NOT rear_side_irradiance_data.get(PHOTOVOLTAIC_MODULE_TYPE_NAME)
# Then use the one from the dictionary which should be Monofacial
# Else :
# Use the rear_side_irradiance_data which should be defined as Bifacial !
photovoltaic_module_type = dictionary.get(PHOTOVOLTAIC_MODULE_TYPE_NAME, None)
technology_name_and_type = dictionary.get(TECHNOLOGY_NAME, None)
photovoltaic_module, mount_type = (
technology_name_and_type.split(":")
if technology_name_and_type
else (None, None)
)
peak_power = str(dictionary.get(PEAK_POWER_COLUMN_NAME, None))
peak_power += f' [dim]{dictionary.get(PEAK_POWER_UNIT_NAME, None)}[/dim]'
algorithms = dictionary.get(POWER_MODEL_COLUMN_NAME, None)
irradiance_data_source = dictionary.get(IRRADIANCE_SOURCE_COLUMN_NAME, None)
radiation_model = dictionary.get(RADIATION_MODEL_COLUMN_NAME, None)
equation = dictionary.get('Equation', None)
timing_algorithm = dictionary.get(TIME_ALGORITHM_COLUMN_NAME, None)
solar_positioning_algorithm = dictionary.get(POSITIONING_ALGORITHM_COLUMN_NAME, None)
adjusted_for_atmospheric_refraction = dictionary.get('Unrefracted ⦧', None)
azimuth_origin = dictionary.get(AZIMUTH_ORIGIN_COLUMN_NAME, None)
## Positions sun-to-horizon : from the set {['Above', 'Low angle', 'Below']}
## for which calculations were performed !
if dictionary.get(SolarPositionParameterMetadataColumnName.sun_horizon_positions, None):
sun_horizon_positions = [position.value for position in dictionary.get(SolarPositionParameterMetadataColumnName.sun_horizon_positions, None)]
else:
sun_horizon_positions = None
incidence_algorithm = dictionary.get(INCIDENCE_ALGORITHM_COLUMN_NAME, None)
shading_algorithm = dictionary.get(SHADING_ALGORITHM_COLUMN_NAME, None)
if dictionary.get(SHADING_STATES_COLUMN_NAME) is not None:
shading_states = [state.value for state in dictionary.get(SHADING_STATES_COLUMN_NAME, None)]
else:
shading_states = None
# Review Me : What does and what does NOT make sense to have separately ?
if rear_side_irradiance_data:
rear_side_peak_power = str(dictionary.get(PEAK_POWER_COLUMN_NAME, None))
rear_side_peak_power += f' [dim]{dictionary.get(PEAK_POWER_UNIT_NAME, None)}[/dim]'
rear_side_algorithms = dictionary.get(POWER_MODEL_COLUMN_NAME, None)
# ------------------------------------------------------------------------
# Build the caption ------------------------------------------------------
if longitude or latitude or elevation:
caption += "[underline]Location[/underline] "
if longitude and latitude:
caption += f"{LONGITUDE_COLUMN_NAME}, {LATITUDE_COLUMN_NAME} = [bold]{longitude}[/bold], [bold]{latitude}[/bold]"
if elevation:
caption += f", Elevation: [bold]{elevation} m[/bold]"
if (
surface_orientation
and surface_tilt
or surface_orientations
and surface_tilts
# val is not None
# # for val in [surface_orientation, surface_tilt, rear_side_surface_orientation, rear_side_surface_tilt]
# for val in [surface_orientation, surface_tilt]
):
caption += "\n[underline]Position[/underline] "
if surface_orientation is not None:
caption += f"{SURFACE_ORIENTATION_COLUMN_NAME}: [bold]{surface_orientation}[/bold], "
if surface_tilt is not None:
caption += f"{SURFACE_TILT_COLUMN_NAME}: [bold]{surface_tilt}[/bold] "
if surface_orientations and surface_tilts:
# Convert np.float64 to regular float, then format
surface_orientations_string = ", ".join(
[f"{float(val):.4f}" for val in surface_orientations]
)
caption += f"{SolarSurfacePositionParameterColumnName.orientations.value}: [bold]{surface_orientations_string}[/bold], "
surface_tilts_string = ", ".join(
[f"{float(val):.4f}" for val in surface_tilts]
)
caption += f"{SolarSurfacePositionParameterColumnName.tilts.value}: [bold]{surface_tilts_string}[/bold] "
# Rear-side ?
if rear_side_irradiance_data:
rear_side_surface_orientation = round_float_values(
rear_side_irradiance_data.get(REAR_SIDE_SURFACE_ORIENTATION_COLUMN_NAME, None),
rounding_places
)
if rear_side_surface_orientation is not None:
caption += (
f", {REAR_SIDE_SURFACE_ORIENTATION_COLUMN_NAME}: [bold]{rear_side_surface_orientation}[/bold], "
)
rear_side_surface_tilt = round_float_values(
rear_side_irradiance_data.get(REAR_SIDE_SURFACE_TILT_COLUMN_NAME, None),
rounding_places
)
if rear_side_surface_tilt is not None:
caption += f"{REAR_SIDE_SURFACE_TILT_COLUMN_NAME}: [bold]{rear_side_surface_tilt}[/bold] "
# Units for both front-side and rear-side too ! Should _be_ the same !
if (
longitude
or latitude
or elevation
or surface_orientation
# or rear_side_surface_orientation
or surface_tilt
# or rear_side_surface_tilt
and angular_units is not None
):
caption += f" [underline]Angular units[/underline] [dim][code]{angular_units}[/code][/dim]"
# Photovoltaic Module
if photovoltaic_module:
caption += "\n[underline]Module[/underline] "
caption += f"Type: [bold]{photovoltaic_module_type}[/bold], "
caption += f"{TECHNOLOGY_NAME}: [bold]{photovoltaic_module}[/bold], "
caption += f"Mount: [bold]{mount_type}[/bold], "
caption += f"{PEAK_POWER_COLUMN_NAME}: [bold]{peak_power}[/bold]"
# Fundamental Definitions
if (
surface_orientation
or surface_tilt
or surface_orientations
or surface_tilts
):
caption += "\n[underline]Definitions[/underline] "
if azimuth_origin:
caption += f"Azimuth origin : [bold blue]{azimuth_origin}[/bold blue], "
if timezone:
if timezone == ZoneInfo('UTC'):
caption += f"[bold]{timezone}[/bold], "
else:
caption += f"Local Zone : [bold]{timezone}[/bold], "
solar_incidence_definition = dictionary.get(INCIDENCE_DEFINITION_COLUMN_NAME, None)
if solar_incidence_definition is not None:
caption += f"{INCIDENCE_DEFINITION}: [bold yellow]{solar_incidence_definition}[/bold yellow], "
solar_constant = dictionary.get(SOLAR_CONSTANT_COLUMN_NAME, None)
eccentricity_phase_offset = dictionary.get(ECCENTRICITY_PHASE_OFFSET_COLUMN_NAME, None)
eccentricity_amplitude = dictionary.get(
ECCENTRICITY_AMPLITUDE_COLUMN_NAME, None
)
if sun_horizon_positions:
caption += f"Sun-to-Horizon: [bold]{sun_horizon_positions}[/bold]"
# Algorithms
if (
algorithms
or radiation_model
or timing_algorithm
or solar_positioning_algorithm
or incidence_algorithm
or shading_algorithm
):
caption += "\n[underline]Algorithms[/underline] "
if algorithms:
caption += f"{POWER_MODEL_COLUMN_NAME}: [bold]{algorithms}[/bold], "
if timing_algorithm:
caption += f"Timing : [bold]{timing_algorithm}[/bold], "
if solar_positioning_algorithm:
caption += f"Positioning : [bold]{solar_positioning_algorithm}[/bold], "
if adjusted_for_atmospheric_refraction:
# caption += f"\n[underline]Atmospheric Properties[/underline] "
caption += f"Adjusted for refraction : [bold]{adjusted_for_atmospheric_refraction}[/bold], "
if incidence_algorithm:
caption += f"Incidence : [bold yellow]{incidence_algorithm}[/bold yellow], "
if shading_algorithm:
caption += f"Shading : [bold]{shading_algorithm}[/bold], "
if shading_states:
caption += f"Shading states : [bold]{shading_states}[/bold]"
# if rear_side_shading_states:
# caption += f"Rear-side Shading states : [bold]{rear_side_shading_states}[/bold]"
# Radiation model
if radiation_model:
caption += f"\n[underline]{RADIATION_MODEL_COLUMN_NAME}[/underline] : [bold]{radiation_model}[/bold], "
irradiance_units = dictionary.get('Unit', UNITLESS)
caption += f"[underline]Irradiance units[/underline] [dim]{irradiance_units}[/dim]"
if equation:
#from rich.markdown import Markdown
#markdown_equation = Markdown(f"{equation}")
caption += f"\nEquation : [dim][code]{equation}[/code][/dim]"
# if rear_side_shading_algorithm:
# caption += f"Rear-side Shading : [bold]{rear_side_shading_algorithm}[/bold]"
# solar_incidence_algorithm = dictionary.get(INCIDENCE_ALGORITHM_COLUMN_NAME, None)
# if solar_incidence_algorithm is not None:
# caption += f"{INCIDENCE_ALGORITHM_COLUMN_NAME}: [bold yellow]{solar_incidence_algorithm}[/bold yellow], "
if any([solar_constant, eccentricity_phase_offset, eccentricity_amplitude]):
caption += "\n[underline]Constants[/underline] "
if solar_constant:
caption += f"{SOLAR_CONSTANT_COLUMN_NAME} : {solar_constant}, "
if eccentricity_phase_offset and eccentricity_amplitude:
caption += f"{ECCENTRICITY_PHASE_OFFSET_SHORT_COLUMN_NAME} : {eccentricity_phase_offset}, "
caption += f"{ECCENTRICITY_AMPLITUDE_COLUMN_NAME} : {eccentricity_amplitude}, "
# Sources ?
if irradiance_data_source:
caption += f"\n{IRRADIANCE_SOURCE_COLUMN_NAME}: [bold]{irradiance_data_source}[/bold], "
return caption.rstrip(", ") # Remove trailing comma + space
columns ¶
Functions:
| Name | Description |
|---|---|
add_key_table_columns | Notes |
add_key_table_columns ¶
add_key_table_columns(
table,
dictionary,
timestamps,
rounding_places,
keys_to_sum: set = KEYS_TO_SUM,
keys_to_average: set = KEYS_TO_AVERAGE,
keys_to_exclude: set = KEYS_TO_EXCLUDE,
) -> RenderableType
Notes
Important : the input dictionary is expected to be a flat one.
Source code in pvgisprototype/cli/print/irradiance/columns.py
def add_key_table_columns(
table,
dictionary,
timestamps,
rounding_places,
keys_to_sum: set = KEYS_TO_SUM,
keys_to_average: set = KEYS_TO_AVERAGE,
keys_to_exclude: set = KEYS_TO_EXCLUDE,
) -> RenderableType:
"""
Notes
-----
Important : the input `dictionary` is expected to be a flat one.
"""
for key, value in dictionary.items():
if value is not None and key not in keys_to_exclude:
# if single numeric or string, generate an array "of it" as long as the timestamps
if isinstance(value, (float, int)):
dictionary[key] = full(len(timestamps), value)
if isinstance(value, str):
dictionary[key] = full(len(timestamps), str(value))
# add sum of value/s to the column footer
if key in keys_to_sum:
if (
isinstance(value, ndarray)
# and not isnan(value).all()
and value.dtype.kind in "if"
):
sum_of_key_value = Text(
str(round_float_values(nansum(value), rounding_places)),
# style="code purple",
style="bold purple",
)
table.add_column(
header=key,
footer=sum_of_key_value, # Place the Sum in the footer
footer_style="white",
no_wrap=False,
)
elif key in keys_to_average:
if (
isinstance(value, ndarray) and value.dtype.kind in "if"
) | isinstance(value, float):
table.add_column(
header=key,
footer=Text(str(nanmean(value))), # Mean of Key Value in the footer
footer_style="italic blue",
no_wrap=False,
)
else:
table.add_column(key, no_wrap=False)
return table
data ¶
Functions:
| Name | Description |
|---|---|
flatten_dictionary | Flatten a nested dictionary |
print_irradiance_table_2 | |
flatten_dictionary ¶
Flatten a nested dictionary
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dictionary | The nested dictionary to flatten | required |
Returns:
| Type | Description |
|---|---|
A flattened dictionary excluding the specified keys | |
Source code in pvgisprototype/cli/print/irradiance/data.py
def flatten_dictionary(dictionary):
"""
Flatten a nested dictionary
Parameters
----------
dictionary: dict
The nested dictionary to flatten
Returns
-------
A flattened dictionary excluding the specified keys
"""
flat_dictionary = {}
def flatten(input_dictionary):
for key, value in input_dictionary.items():
if isinstance(value, dict):
flatten(value)
else:
# Discard empty arrays
if isinstance(value, ndarray):
if value.size == 0:
continue
# Discard arrays that are all NaN
elif issubclass(value.dtype.type, (float, int)) and isnan(value).all():
continue
else:
flat_dictionary[key] = value
else:
flat_dictionary[key] = value
flatten(dictionary)
return flat_dictionary
print_irradiance_table_2 ¶
print_irradiance_table_2(
title: str | None = "Power & Irradiance",
irradiance_data: dict = dict(),
rear_side_irradiance_data: dict = dict(),
longitude=None,
latitude=None,
elevation=None,
timestamps: Timestamp | DatetimeIndex = now(),
user_requested_timestamps=None,
timezone: ZoneInfo | None = None,
user_requested_timezone=None,
rounding_places: int = ROUNDING_PLACES_DEFAULT,
index: bool = False,
verbose=1,
) -> None
Source code in pvgisprototype/cli/print/irradiance/data.py
def print_irradiance_table_2(
title: str | None = "Power & Irradiance",
irradiance_data: dict = dict(),
rear_side_irradiance_data: dict = dict(),
longitude=None,
latitude=None,
elevation=None,
timestamps: Timestamp | DatetimeIndex = Timestamp.now(),
user_requested_timestamps=None,
timezone: ZoneInfo | None = None,
user_requested_timezone=None,
rounding_places: int = ROUNDING_PLACES_DEFAULT,
index: bool = False,
verbose=1,
) -> None:
""" """
irradiance_data = flatten_dictionary(irradiance_data)
# in case of multiple solar surfaces :
# iterate over each pair of (surface_orientation, surface_tilt)
surface_orientation = irradiance_data.get(SolarSurfacePositionParameter.surface_orientation, None)
surface_tilt = irradiance_data.get(SolarSurfacePositionParameter.surface_tilt, None)
for _pair, (_surface_orientation_value, _surface_tilt_value) in enumerate(
zip([surface_orientation], [surface_tilt])
):
longitude = round_float_values(longitude, rounding_places)
latitude = round_float_values(latitude, rounding_places)
elevation = round_float_values(elevation, 0) # rounding_places)
# Caption
caption = build_caption_for_irradiance_data(
longitude=longitude,
latitude=latitude,
elevation=elevation,
timezone=timezone,
dictionary=irradiance_data,
rear_side_irradiance_data=rear_side_irradiance_data,
rounding_places=rounding_places,
)
# then : create a Legend table for the symbols in question
legend = build_legend_table(
dictionary=irradiance_data,
caption=caption,
show_sum=True,
show_mean=True,
show_header=False,
box=None,
)
# Define the time column name based on the timezone or user requests
time_column_name = TIME_COLUMN_NAME if user_requested_timestamps is None else LOCAL_TIME_COLUMN_NAME
# Build the irradiance table/s
table = build_irradiance_table(
title=title,
index=index,
dictionary=irradiance_data,
timestamps=timestamps,
rounding_places=rounding_places,
time_column_name=time_column_name,
time_column_footer=f"{SYMBOL_SUMMATION} / [blue]{SYMBOL_MEAN}[/blue]", # Abusing this "cell" as a "Row Name"
time_column_footer_style = "purple", # to make it somehow distinct from the Column !
keys_to_sum = KEYS_TO_SUM,
keys_to_average = KEYS_TO_AVERAGE,
keys_to_exclude = KEYS_TO_EXCLUDE,
)
if rear_side_irradiance_data:
rear_side_table = build_irradiance_table(
title=f'Rear-side {title}',
index=index,
dictionary=irradiance_data,
timestamps=timestamps,
rounding_places=rounding_places,
time_column_name=time_column_name,
time_column_footer=f"{SYMBOL_SUMMATION} / [blue]{SYMBOL_MEAN}[/blue]",
time_column_footer_style = "purple",
keys_to_sum = REAR_SIDE_KEYS_TO_SUM,
keys_to_average = KEYS_TO_AVERAGE,
keys_to_exclude = KEYS_TO_EXCLUDE,
)
# totals_table = Table(
# title=f'Total {title}',
# # caption=caption.rstrip(', '), # Remove trailing comma + space
# caption_justify="left",
# expand=False,
# padding=(0, 1),
# box=SIMPLE_HEAD,
# header_style="bold gray50",
# show_footer=True,
# footer_style='white',
# row_styles=["none", "dim"],
# highlight=True,
# )
# if index:
# totals_table.add_column("")
# totals_table.add_column("")
else:
rear_side_table = None # in order to avoid the "unbound error"
# totals_table = None
# Populate table/s
table = populate_irradiance_table(
table=table,
dictionary=irradiance_data,
timestamps=timestamps,
index=index,
rounding_places=rounding_places,
keys_to_exclude=KEYS_TO_EXCLUDE,
)
if rear_side_table:
rear_side_table = populate_irradiance_table(
table=rear_side_table,
dictionary=rear_side_irradiance_data,
timestamps=timestamps,
index=index,
rounding_places=rounding_places,
)
# Print if requested via at least 1x `-v`
if verbose:
print_table_and_legend(
caption=caption,
table=table,
rear_side_table=rear_side_table,
legend=legend,
)
table ¶
Functions:
| Name | Description |
|---|---|
build_irradiance_table | |
populate_irradiance_table | |
print_irradiance_xarray | Print the irradiance time series in a formatted table with each center wavelength as a column. |
print_table_and_legend | Print panels for both caption and legend |
build_irradiance_table ¶
build_irradiance_table(
title: str | None,
index: bool,
dictionary,
timestamps,
rounding_places,
keys_to_sum: dict,
keys_to_average: dict,
keys_to_exclude: dict,
time_column_name: RenderableType = "Time",
time_column_footer: RenderableType = SYMBOL_SUMMATION,
time_column_footer_style: str = "purple",
) -> RenderableType
Source code in pvgisprototype/cli/print/irradiance/table.py
def build_irradiance_table(
title: str | None,
index: bool,
dictionary,
timestamps,
rounding_places,
keys_to_sum: dict,
keys_to_average: dict,
keys_to_exclude: dict,
time_column_name: RenderableType = "Time",
time_column_footer: RenderableType = SYMBOL_SUMMATION,
time_column_footer_style: str = "purple",
) -> RenderableType:
"""
"""
table = Table(
title=title,
# caption=caption.rstrip(', '), # Remove trailing comma + space
caption_justify="left",
expand=False,
padding=(0, 1),
box=SIMPLE_HEAD,
header_style="bold gray50",
show_footer=True,
footer_style='white',
row_styles=["none", "dim"],
highlight=True,
)
# base columns
if index:
table.add_column("Index")
## Time column
table.add_column(
time_column_name,
no_wrap=True,
footer=time_column_footer,
footer_style=time_column_footer_style,
)
# remove the 'Title' entry! ---------------------------------------------
dictionary.pop("Title", NOT_AVAILABLE)
# ------------------------------------------------------------- Important
# add and process additional columns
table = add_key_table_columns(
table=table,
dictionary=dictionary,
timestamps=timestamps,
rounding_places=rounding_places,
keys_to_sum=keys_to_sum,
keys_to_average=keys_to_average,
keys_to_exclude=keys_to_exclude,
)
return table
populate_irradiance_table ¶
populate_irradiance_table(
table,
dictionary,
timestamps,
index,
rounding_places,
keys_to_exclude: set = KEYS_TO_EXCLUDE,
) -> RenderableType
Source code in pvgisprototype/cli/print/irradiance/table.py
def populate_irradiance_table(
table,
dictionary,
timestamps,
index,
rounding_places,
keys_to_exclude: set = KEYS_TO_EXCLUDE,
) -> RenderableType:
"""
"""
# Zip series and timestamps
filtered_dictionary = {
key: numpy.atleast_1d(value) for key, value in dictionary.items()
if key not in keys_to_exclude and value is not None
}
none_keys = [key for key, value in filtered_dictionary.items() if value is None]
if none_keys:
raise ValueError(f"The following keys are of `NoneType` which is not iterable and thus cannot be zipped: {none_keys}")
zipped_series = zip(*filtered_dictionary.values())
zipped_data = zip(timestamps, zipped_series)
index_counter = 1
for timestamp, values in zipped_data:
row = []
if index:
row.append(str(index_counter))
index_counter += 1
row.append(to_datetime(timestamp).strftime("%Y-%m-%d %H:%M:%S"))
for idx, (column_name, value) in enumerate(
zip(filtered_dictionary.keys(), values)
):
# First row of the table is the header
if idx == 0: # assuming after 'Time' is the value of main interest
# Make first row item bold
bold_value = Text(
str(round_float_values(value, rounding_places)), style="bold dark_orange",
)
row.append(bold_value)
else:
# if not isinstance(value, str) or isinstance(value, float):
row.append(
format_string(
value=value,
column_name=column_name,
rounding_places=rounding_places,
)
)
table.add_row(*row)
return table
print_irradiance_xarray ¶
print_irradiance_xarray(
location_time_series: DataArray,
longitude=None,
latitude=None,
elevation=None,
title: str | None = "Irradiance data",
rounding_places: int = 3,
verbose: int = 1,
index: bool = False,
) -> None
Print the irradiance time series in a formatted table with each center wavelength as a column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
location_time_series | DataArray | The time series data with dimensions (time, center_wavelength). | required |
longitude | float | The longitude of the location. | None |
latitude | float | The latitude of the location. | None |
elevation | float | The elevation of the location. | None |
title | str | The title of the table. | 'Irradiance data' |
rounding_places | int | The number of decimal places to round to. | 3 |
verbose | int | Verbosity level. | 1 |
index | bool | Whether to show an index column. | False |
Source code in pvgisprototype/cli/print/irradiance/table.py
def print_irradiance_xarray(
location_time_series: DataArray,
longitude=None,
latitude=None,
elevation=None,
# coordinate: str = None,
title: str | None = "Irradiance data",
rounding_places: int = 3,
verbose: int = 1,
index: bool = False,
) -> None:
"""
Print the irradiance time series in a formatted table with each center wavelength as a column.
Parameters
----------
location_time_series : xr.DataArray
The time series data with dimensions (time, center_wavelength).
longitude : float, optional
The longitude of the location.
latitude : float, optional
The latitude of the location.
elevation : float, optional
The elevation of the location.
title : str, optional
The title of the table.
rounding_places : int, optional
The number of decimal places to round to.
verbose : int, optional
Verbosity level.
index : bool, optional
Whether to show an index column.
"""
console = Console()
# Extract relevant data from the location_time_series
# Prepare the table
table = Table(
title=title,
caption_justify="left",
expand=False,
padding=(0, 1),
box=SIMPLE_HEAD,
show_footer=True,
)
if index:
table.add_column("Index")
if 'time' in location_time_series.dims:
table.add_column("Time", footer=f"{SYMBOL_SUMMATION}")
# Add columns for each center wavelength (irradiance wavelength)
if 'center_wavelength' in location_time_series.coords:
center_wavelengths = location_time_series.coords['center_wavelength'].values
if center_wavelengths.size > 0:
for wavelength in center_wavelengths:
table.add_column(f"{wavelength:.0f} nm", justify="right")
else:
logger.warning("No center_wavelengths found in the dataset.")
else:
logger.warning("No 'center_wavelength' coordinate found in the dataset.")
# Populate the table with the irradiance data
# case of scalar data
if 'time' not in location_time_series.dims:
row = []
if index:
row.append("1") # Single row for scalar data
# Handle the presence of a coordinate (like center_wavelength)
# if coordinate ... ?
if 'center_wavelength' in location_time_series.coords:
for irradiance_value in location_time_series.values:
row.append(f"{round(irradiance_value, rounding_places):.{rounding_places}f}")
else:
row.append(f"{round(location_time_series.item(), rounding_places):.{rounding_places}f}")
table.add_row(*row)
else:
irradiance_values = location_time_series.values
for idx, timestamp in enumerate(location_time_series.time.values):
row = []
if index:
row.append(str(idx + 1))
# Convert timestamp to string format
try:
row.append(to_datetime(timestamp).strftime("%Y-%m-%d %H:%M:%S"))
except Exception as e:
logger.error(f"Invalid timestamp at index {idx}: {e}")
row.append("Invalid timestamp")
if 'center_wavelength' in location_time_series.coords:
# add data variable values for each "coordinate" value at this timestamp
# i.e. : irradiance values for each "center_wavelength" at this timestamp
for irradiance_value in irradiance_values[idx]:
row.append(f"{round(irradiance_value, rounding_places):.{rounding_places}f}")
else:
# a scalar, i.e. no Xarray "coordinate"
row.append(f"{round(irradiance_values[idx], rounding_places):.{rounding_places}f}")
table.add_row(*row)
# Prepare a caption with the location information
caption = str()
if longitude is not None and latitude is not None:
caption += f"Location Longitude ϑ, Latitude ϕ = {longitude}, {latitude}"
if elevation is not None:
caption += f", Elevation: {elevation} m"
caption += "\nLegend: Center Wavelengths (nm)"
if verbose:
console.print(table)
console.print(Panel(caption, expand=False))
print_table_and_legend ¶
print_table_and_legend(
caption: RenderableType,
table: RenderableType,
rear_side_table: RenderableType | None,
legend: RenderableType,
caption_subtitle: str = "Reference",
legend_subtitle: str = "Legend",
) -> None
Print panels for both caption and legend
Source code in pvgisprototype/cli/print/irradiance/table.py
def print_table_and_legend(
caption: RenderableType,
table: RenderableType,
rear_side_table: RenderableType | None,
legend: RenderableType,
caption_subtitle: str = 'Reference',
legend_subtitle: str = 'Legend',
) -> None:
"""
Print panels for both caption and legend
"""
Console().print(table)
if rear_side_table:
Console().print(rear_side_table)
panels = []
if caption:
caption_panel = Panel(
caption,
subtitle=f"[gray]{caption_subtitle}[/gray]",
subtitle_align="right",
border_style="dim",
expand=False
)
panels.append(caption_panel)
if legend:
legend_panel = Panel(
legend,
subtitle=f"[dim]{legend_subtitle}[/dim]",
subtitle_align="right",
border_style="dim",
expand=False,
padding=(0,1),
# style="dim",
)
panels.append(legend_panel)
# Use Columns to place them side-by-side
from rich.columns import Columns
Console().print(Columns(panels))
text ¶
Functions:
| Name | Description |
|---|---|
format_string | |
format_string ¶
format_string(
value: str | int | float | Style,
column_name=None,
rounding_places=ROUNDING_PLACES_DEFAULT,
)
Source code in pvgisprototype/cli/print/irradiance/text.py
def format_string(
value: str | int | float | Style,
# enum_model: EnumType,
column_name=None,
rounding_places=ROUNDING_PLACES_DEFAULT,
):
"""
"""
# Handle None
if value is None:
return ""
if isinstance(value, (EnumType, str)):
if value in [string.value for string in ShadingState]:
style = SHADING_STATE_COLOR_MAP.get(value, None)
return Text(value, style=style)
# Handle SolarEvent
if value in [string.value for string in SolarEvent]:
style = SOLAR_EVENT_COLOR_MAP.get(value, None)
return Text(value, style=style)
# Handle SunHorizonPositionModel
if value in [string.value for string in SunHorizonPositionModel]:
style = str(SUN_HORIZON_POSITION_COLOR_MAP.get(str(value)))
return Text(value, style=style)
# Handle negative numbers or loss columns
else:
rounded_value = round_float_values(value, rounding_places)
# If values of this column are negative / represent loss
if (
f" {SYMBOL_LOSS}" in column_name
or f"{SYMBOL_REFLECTIVITY}" in column_name
or value < 0
): # Avoid matching any `-`
# Make them bold red
return Text(str(rounded_value), style="bold red")
elif isinstance(value, (numpy.number, float)) and value ==0.:
return Text(str(rounded_value), style="dim gray")
elif (isinstance(value, float) and (math.isnan(value))) or (
isinstance(value, numpy.floating) and numpy.isnan(value)
):
return Text(str(value), style="dim red")
else:
return str(rounded_value)
# Fallback: just return as string
return str(value)
legend ¶
Functions:
| Name | Description |
|---|---|
build_legend_table | |
build_legend_table ¶
build_legend_table(
dictionary: dict,
caption: str,
show_sum: bool = False,
show_mean: bool = False,
show_header: bool = False,
box: str | None = None,
)
Source code in pvgisprototype/cli/print/legend.py
def build_legend_table(
dictionary: dict,
caption: str,
show_sum: bool = False,
show_mean: bool = False,
show_header: bool = False,
box: str | None = None, # box=SIMPLE_HEAD,
):
"""
"""
# from rich import print
# for key, value in dictionary.items():
# print(f"{key=} : {value=}")
# none_keys = [key for key, value in dictionary.items() if value is None]
# if none_keys:
# raise ValueError(f"The following keys are of `NoneType` which is not iterable and thus cannot be zipped: {none_keys}")
# first : Identify symbols in the input dictionary
filtered_symbols = {
symbol: description
for symbol, description in SYMBOL_DESCRIPTIONS.items()
if any(symbol in key for key in dictionary.keys())
}
# Check for SYMBOL_SUMMATION in the input dictionary before adding
if show_sum or any(SYMBOL_SUMMATION in key for key in dictionary.keys()):
filtered_symbols[SYMBOL_SUMMATION] = f"[purple]{SYMBOL_SUMMATION_NAME}[/purple]"
# Check for SYMBOL_MEAN in the input dictionary before adding
if show_mean or any(SYMBOL_MEAN in key for key in dictionary.keys()):
filtered_symbols[SYMBOL_MEAN] = f"[blue]{SYMBOL_MEAN_NAME}[/blue]"
# then : Create a Legend table for the symbols in question
legend = Table(
# title='Legend',
# title="[code]Legend[/code]",
# caption="Caption text",
show_header=show_header,
# header_style="dim",
# row_styles=["none", "dim"],
box=box,
# highlight=True,
# pad_edge=False,
# collapse_padding=True,
)
# next : Determine the number of columns based on the "height" of Caption
if len(caption.splitlines()) == 0:
return None
else:
number_of_symbols = len(filtered_symbols)
number_of_rows = len(caption.splitlines())
number_of_columns = ceil(number_of_symbols / number_of_rows) * 2 # Multiply by 2 for Symbol & Description pairs
for _ in range(number_of_columns // 2):
legend.add_column("Symbol", justify="center", style="bold blue", no_wrap=True)
legend.add_column("Description", justify="left", style="dim", no_wrap=False)
# finally : Populate the Legend table row by row
rows = [["" for _ in range(number_of_columns)] for _ in range(number_of_rows)] # Empty table grid
current_row = 0 # Start with the first row
current_column = 0 # Start with the first column pair
for symbol, description in filtered_symbols.items():
rows[current_row][current_column * 2] = f"[yellow]{symbol}[/yellow]" # Symbol column
if description == SYMBOL_POWER_NAME:
rows[current_row][current_column * 2 + 1] = f"[dark_orange]{description}[/dark_orange]" # Description column
elif description == SYMBOL_LOSS_NAME:
rows[current_row][current_column * 2 + 1] = f"[red bold]{description}[/red bold]" # Description column
else:
rows[current_row][current_column * 2 + 1] = description # Description column
current_row += 1
if current_row >= number_of_rows: # Move to the next column if rows are filled
current_row = 0
current_column += 1
# Add rows to the legend table
for row in rows:
legend.add_row(*row)
return legend
metadata ¶
Functions:
| Name | Description |
|---|---|
print_command_metadata | |
print_command_metadata ¶
Source code in pvgisprototype/cli/print/metadata.py
def print_command_metadata(context: Context):
""" """
command_parameters = {}
command_parameters["command"] = context.command_path
command_parameters = command_parameters | context.params
command_parameters_panel = Panel.fit(
Pretty(command_parameters, no_wrap=True),
subtitle="[reverse]Command Metadata[/reverse]",
subtitle_align="right",
border_style="dim",
style="dim",
)
Console().print(command_parameters_panel)
# write to file ?
import json
from pvgisprototype.validation.serialisation import CustomEncoder
with open("command_parameters.json", "w") as json_file:
json.dump(command_parameters, json_file, cls=CustomEncoder, indent=4)
panels ¶
Functions:
| Name | Description |
|---|---|
build_version_and_fingerprint_columns | Combine software version and fingerprint panels into a single Columns |
build_version_and_fingerprint_panels | Dynamically build panels based on available data. |
build_version_and_fingerprint_columns ¶
build_version_and_fingerprint_columns(
version: bool = False, fingerprint: bool = False
) -> Columns
Combine software version and fingerprint panels into a single Columns object.
Source code in pvgisprototype/cli/print/panels.py
def build_version_and_fingerprint_columns(
version:bool = False,
fingerprint: bool = False,
) -> Columns:
"""Combine software version and fingerprint panels into a single Columns
object."""
version_and_fingeprint_panels = build_version_and_fingerprint_panels(
version=version,
fingerprint=fingerprint,
)
return Columns(version_and_fingeprint_panels, expand=False, padding=2)
build_version_and_fingerprint_panels ¶
build_version_and_fingerprint_panels(
version: bool = False, fingerprint: bool = False
) -> list[Panel]
Dynamically build panels based on available data.
Source code in pvgisprototype/cli/print/panels.py
def build_version_and_fingerprint_panels(
version:bool = False,
fingerprint: bool = False,
) -> list[Panel]:
"""Dynamically build panels based on available data."""
# Always yield version panel
panels = []
if version:
panels.append(build_pvgis_version_panel())
# Yield fingerprint panel only if fingerprint is provided
if fingerprint:
panels.append(build_fingerprint_panel(fingerprint))
return panels
performance ¶
Modules:
| Name | Description |
|---|---|
analysis | |
horizon | |
metadata | |
photovoltaic_module | |
position | |
table | |
analysis ¶
Functions:
| Name | Description |
|---|---|
print_change_percentages_panel | Print a formatted table of photovoltaic performance metrics using the |
print_change_percentages_panel ¶
print_change_percentages_panel(
photovoltaic_power: PhotovoltaicPower,
title: str = "Analysis of Performance",
longitude=None,
latitude=None,
elevation=None,
surface_orientation: float | bool = True,
surface_tilt: float | bool = True,
horizon_profile: NDArray | None = None,
timestamps: DatetimeIndex | None = None,
timezone: ZoneInfo | None = None,
angle_output_units: str = RADIANS,
rounding_places: int = 1,
verbose=1,
index: bool = False,
version: bool = False,
fingerprint: bool = False,
quantity_style="magenta",
value_style="cyan",
unit_style="cyan",
percentage_style="dim",
reference_quantity_style="white",
)
Print a formatted table of photovoltaic performance metrics using the Rich library.
Analyse the photovoltaic performance in terms of :
- In-plane (or inclined) irradiance without reflectivity loss
- Reflectivity effect as a function of the solar incidence angle
- Irradiance after reflectivity effect
- Spectral effect due to variation in the natural sunlight spectrum and its difference to standardised artificial laborary light spectrum
- Effective irradiance = Inclined irradiance + Reflectivity effect + Spectral effect
- Loss as a function of the PV module temperature and low irradiance effects
- Conversion of the effective irradiance to photovoltaic power
- Total net effect = Reflectivity, Spectral effect, Temperature & Low irradiance
Finally, report the photovoltaic power output after system loss and degradation with age
Source code in pvgisprototype/cli/print/performance/analysis.py
def print_change_percentages_panel(
# dictionary: dict = dict(),
photovoltaic_power: PhotovoltaicPower,
title: str = "Analysis of Performance",
longitude=None,
latitude=None,
elevation=None,
surface_orientation: float | bool = True,
surface_tilt: float | bool = True,
horizon_profile: NDArray | None = None,
timestamps: DatetimeIndex | None = None,
timezone: ZoneInfo | None = None,
angle_output_units: str = RADIANS,
rounding_places: int = 1, # ROUNDING_PLACES_DEFAULT,
verbose=1,
index: bool = False,
version: bool = False,
fingerprint: bool = False,
quantity_style="magenta",
value_style="cyan",
unit_style="cyan",
percentage_style="dim",
reference_quantity_style="white",
):
"""Print a formatted table of photovoltaic performance metrics using the
Rich library.
Analyse the photovoltaic performance in terms of :
- In-plane (or inclined) irradiance without reflectivity loss
- Reflectivity effect as a function of the solar incidence angle
- Irradiance after reflectivity effect
- Spectral effect due to variation in the natural sunlight spectrum and its
difference to standardised artificial laborary light spectrum
- Effective irradiance = Inclined irradiance + Reflectivity effect + Spectral effect
- Loss as a function of the PV module temperature and low irradiance effects
- Conversion of the effective irradiance to photovoltaic power
- Total net effect = Reflectivity, Spectral effect, Temperature & Low
irradiance
Finally, report the photovoltaic power output after system loss and
degradation with age
"""
frequency, frequency_label = determine_frequency(timestamps=timestamps)
add_empty_row_before = {
# IN_PLANE_IRRADIANCE,
REFLECTIVITY,
# IRRADIANCE_AFTER_REFLECTIVITY,
SPECTRAL_EFFECT_NAME,
# EFFECTIVE_IRRADIANCE_NAME,
TEMPERATURE_AND_LOW_IRRADIANCE_COLUMN_NAME,
# PHOTOVOLTAIC_POWER_WITHOUT_SYSTEM_LOSS_COLUMN_NAME,
SYSTEM_LOSS,
# PHOTOVOLTAIC_POWER_LONG_NAME,
# f"[white dim]{POWER_NAME}",
f"[green bold]{ENERGY_NAME_WITH_SYMBOL}",
# f"[white dim]{NET_EFFECT}",
}
performance_table = build_performance_table(
frequency_label=frequency_label,
quantity_style=quantity_style,
value_style=value_style,
unit_style=unit_style,
mean_value_unit_style="white dim",
percentage_style=percentage_style,
# reference_quantity_style=reference_quantity_style,
)
results = report_photovoltaic_performance(
dictionary=photovoltaic_power,
timestamps=timestamps,
frequency=frequency,
verbose=verbose,
)
# Add rows based on the dictionary keys and corresponding values
for label, (
(value, value_style),
(unit, unit_style),
(mean_value, mean_value_style),
(mean_value_unit, mean_value_unit_style),
standard_deviation,
percentage,
style,
reference_quantity,
input_series,
source,
) in results.items():
if label in add_empty_row_before:
performance_table.add_row()
add_table_row(
table=performance_table,
quantity=label,
value=value,
unit=unit,
mean_value=mean_value,
mean_value_unit=mean_value_unit,
standard_deviation=standard_deviation,
percentage=percentage,
reference_quantity=reference_quantity,
series=input_series,
timestamps=timestamps,
frequency=frequency,
source=source,
quantity_style=quantity_style,
value_style=value_style,
unit_style=unit_style,
mean_value_style=mean_value_style,
mean_value_unit_style=mean_value_unit_style,
percentage_style=percentage_style,
reference_quantity_style=reference_quantity_style,
rounding_places=rounding_places,
)
# Positioning
position_table = build_position_table()
positioning_rounding_places = 3
position_table = populate_position_table(
table=position_table,
data_model=photovoltaic_power,
latitude=latitude,
longitude=longitude,
elevation=elevation,
surface_orientation=surface_orientation,
surface_tilt=surface_tilt,
rounding_places=positioning_rounding_places,
)
position_panel = build_position_panel(position_table, width=performance_table.width)
# Algorithmic metadata panel
algorithmic_metadata_table = populate_algorithmic_metadata_table(
data_model=photovoltaic_power
)
algorithmic_metadata_panel = build_algorithmic_metadata_panel(
algorithmic_metadata_table
)
# Horizon profile
if horizon_profile is not None:
horizon_profile_polar_plot = generate_horizon_profile_polar_plot(horizon_profile)
# horizon_profile_table = build_horizon_profile_table()
# horizon_profile_table.add_row(
# # f"{horizon_profile_polar_plot}",
# horizon_profile_polar_plot
# )
horizon_profile_panel = build_horizon_profile_panel(
# horizon_profile_table
horizon_profile_polar_plot
)
else:
horizon_profile_panel = None
if algorithmic_metadata_panel and horizon_profile_panel is not None:
metadata_columns = Columns([
algorithmic_metadata_panel,
horizon_profile_panel,
])
else:
metadata_columns = None
# Timing
time_table = build_time_table()
time_table = populate_time_table(
table=time_table, timestamps=timestamps, timezone=timezone
)
time_panel = build_time_panel(time_table)
# Photovoltaic Module
photovoltaic_module_table = build_photovoltaic_module_table()
photovoltaic_module_table = populate_photovoltaic_module_table(
table=photovoltaic_module_table,
photovoltaic_power=photovoltaic_power,
)
photovoltaic_module_panel = build_photovoltaic_module_panel(
photovoltaic_module_table
)
# panels = [position_panel, time_panel, photovoltaic_module_panel]
# columns = Columns(
# panels,
# # expand=True,
# # equal=True,
# padding=2,
# )
performance_panel = Panel(
performance_table,
title=title,
expand=False,
# style="on black",
)
photovoltaic_module_columns = Columns(
[
panel
for panel in [
position_panel,
time_panel,
photovoltaic_module_panel,
]
if panel
],
# expand=True,
# equal=True,
padding=3,
)
fingerprint = retrieve_fingerprint(dictionary=photovoltaic_power.output)
version_and_fingerprint_columns = build_version_and_fingerprint_columns(
version=version,
fingerprint=fingerprint,
)
from rich.console import Group
group_panels = [
panel
for panel in [
photovoltaic_module_columns,
performance_panel,
# algorithmic_metadata_panel,
metadata_columns,
# horizon_profile_panel,
version_and_fingerprint_columns,
]
if panel is not None
]
group = Group(*group_panels)
# panel_group = Group(
# Panel(
# performance_table,
# title='Analysis of Performance',
# expand=False,
# # style="on black",
# ),
# columns,
# # Panel(table),
# # Panel(position_panel),
# # Panel("World", style="on red"),
# fit=False
# )
# Console().print(panel_group)
# Console().print(Panel(performance_table))
Console().print(group)
horizon ¶
Functions:
| Name | Description |
|---|---|
build_horizon_profile_panel | |
build_horizon_profile_table | |
generate_horizon_profile_polar_plot | |
build_horizon_profile_panel ¶
Source code in pvgisprototype/cli/print/performance/horizon.py
build_horizon_profile_table ¶
Source code in pvgisprototype/cli/print/performance/horizon.py
def build_horizon_profile_table() -> Table:
""" """
horizon_profile_table = Table(
box=None,
show_header=True,
header_style="bold dim",
show_edge=False,
pad_edge=False,
)
horizon_profile_table.add_column(
f"{HORIZON_HEIGHT_NAME}",
# justify="right",
style="bold", no_wrap=True
)
return horizon_profile_table
generate_horizon_profile_polar_plot ¶
Source code in pvgisprototype/cli/print/performance/horizon.py
def generate_horizon_profile_polar_plot(
horizon_profile,
) -> Plot:
"""
"""
azimuthal_directions_radians = linspace(0, 2 * pi, horizon_profile.size)
from pvgisprototype.cli.plot.uniplot import Plot
horizon_profile_polar_plot = Plot(
xs=degrees(azimuthal_directions_radians),
ys=horizon_profile,
lines=True,
width=45,
height=3,
x_gridlines=[],
y_gridlines=[],
character_set="braille",
# color=[colors[1]],
# legend_labels=[labels[1]],
color=["blue"], # Add color
legend_labels=["Horizon Profile"], # Add legend
interactive=False,
)
return horizon_profile_polar_plot
metadata ¶
Functions:
| Name | Description |
|---|---|
build_algorithmic_metadata_panel | |
build_algorithmic_metadata_table | |
populate_algorithmic_metadata_table | |
build_algorithmic_metadata_panel ¶
Source code in pvgisprototype/cli/print/performance/metadata.py
def build_algorithmic_metadata_panel(algorithmic_metadata_table) -> Panel | None:
""" """
if algorithmic_metadata_table is None:
return None
else:
return Panel(
algorithmic_metadata_table,
subtitle="Algorithmic metadata",
subtitle_align="right",
# box=None,
safe_box=True,
style="",
expand=False,
padding=(0, 3),
)
build_algorithmic_metadata_table ¶
Source code in pvgisprototype/cli/print/performance/metadata.py
def build_algorithmic_metadata_table() -> Table:
""" """
algorithmic_metadata_table = Table(
box=None,
show_header=True,
header_style="bold dim",
show_edge=False,
pad_edge=False,
)
algorithmic_metadata_table.add_column(
f"{TIMING_ALGORITHM_NAME}", justify="center", style="bold", no_wrap=True
)
algorithmic_metadata_table.add_column(
f"{POSITIONING_ALGORITHM_NAME}", justify="center", style="bold", no_wrap=True
)
# algorithmic_metadata_table.add_column(
# # f"{INCIDENCE_ALGORITHM_NAME}", justify="center", style="bold", no_wrap=True
# f"{INCIDENCE_NAME}", justify="center", style="bold", no_wrap=True
# )
algorithmic_metadata_table.add_column(
f"{AZIMUTH_ORIGIN_NAME}", justify="center", style="bold", no_wrap=True
)
algorithmic_metadata_table.add_column(
f"{INCIDENCE_DEFINITION}", justify="center", style="bold", no_wrap=True
)
algorithmic_metadata_table.add_column(
f"{SHADING_ALGORITHM_NAME}", justify="center", style="bold", no_wrap=True
)
# algorithmic_metadata_table.add_column(
# f"{HORIZON_HEIGHT_NAME}", justify="center", style="bold", no_wrap=True
# )
return algorithmic_metadata_table
populate_algorithmic_metadata_table ¶
Source code in pvgisprototype/cli/print/performance/metadata.py
def populate_algorithmic_metadata_table(
data_model: PhotovoltaicPower,
) -> Table | None:
"""
"""
timing_algorithm = data_model.solar_timing_algorithm
positioning_algorithm = data_model.solar_positioning_algorithm
azimuth_origin = data_model.solar_azimuth.origin
incidence_angle_definition = data_model.solar_incidence_definition
incidence_algorithm = data_model.solar_incidence_model
shading_algorithm = data_model.shading_algorithm
if all(
[
timing_algorithm,
positioning_algorithm,
azimuth_origin,
incidence_angle_definition,
incidence_algorithm,
]
):
algorithmic_metadata_table = build_algorithmic_metadata_table()
algorithmic_metadata_table.add_row(
f"{timing_algorithm}",
f"{positioning_algorithm}",
# f"{incidence_algorithm}",
f"{azimuth_origin}",
f"{incidence_angle_definition}, {incidence_algorithm}",
f"{shading_algorithm}",
)
return algorithmic_metadata_table
else:
return None
photovoltaic_module ¶
Functions:
| Name | Description |
|---|---|
build_photovoltaic_module_panel | |
build_photovoltaic_module_table | |
populate_photovoltaic_module_table | |
build_photovoltaic_module_panel ¶
Source code in pvgisprototype/cli/print/performance/photovoltaic_module.py
build_photovoltaic_module_table ¶
Source code in pvgisprototype/cli/print/performance/photovoltaic_module.py
def build_photovoltaic_module_table() -> Table:
""" """
photovoltaic_module_table = Table(
box=None,
show_header=True,
header_style=None,
show_edge=False,
pad_edge=False,
)
photovoltaic_module_table.add_column("Tech", justify="right", style="bold")
photovoltaic_module_table.add_column("Peak-Power", justify="center", style="bold")
photovoltaic_module_table.add_column("Mount Type", justify="left", style="bold")
return photovoltaic_module_table
populate_photovoltaic_module_table ¶
Source code in pvgisprototype/cli/print/performance/photovoltaic_module.py
def populate_photovoltaic_module_table(
table: Table,
photovoltaic_power: PhotovoltaicPower,
) -> Table:
""" """
photovoltaic_module, mount_type = photovoltaic_power.technology.split(":")
# peak_power_unit = photovoltaic_power.presentation.peak_power_unit
peak_power_unit = "Peak Power Unit"
table.add_row(
photovoltaic_module,
f"[green]{photovoltaic_power.peak_power}[/green] {peak_power_unit}",
mount_type,
)
return table
position ¶
Functions:
| Name | Description |
|---|---|
build_position_panel | |
build_position_table | |
populate_position_table | Populate the 'position' table for a photovoltaic module using attributes |
build_position_panel ¶
Source code in pvgisprototype/cli/print/performance/position.py
def build_position_panel(position_table, width) -> Panel:
""" """
return Panel(
position_table,
# title="Positioning", # Add title to provide context without being too bold
# title_align="left", # Align the title to the left
subtitle="Solar Surface",
subtitle_align="right",
# box=None,
safe_box=True,
style="",
border_style="dim", # Soften the panel with a dim border style
# expand=False,
# expand=True,
padding=(0, 2),
width=width,
)
build_position_table ¶
Source code in pvgisprototype/cli/print/performance/position.py
def build_position_table() -> Table:
""" """
position_table = Table(
box=None,
show_header=True,
header_style="bold dim",
show_edge=False,
pad_edge=False,
)
position_table.add_column(
f"{LATITUDE_NAME}", justify="center", style="bold", no_wrap=True
)
position_table.add_column(
f"{LONGITUDE_NAME}", justify="center", style="bold", no_wrap=True
)
position_table.add_column(
f"{ELEVATION_NAME}", justify="center", style="bold", no_wrap=True
)
position_table.add_column(
f"{ORIENTATION_NAME}", justify="center", style="bold", no_wrap=True
)
position_table.add_column(
f"{TILT_NAME}", justify="center", style="bold", no_wrap=True
)
position_table.add_column(
f"{UNIT_NAME}", justify="center", style="dim", no_wrap=True
)
return position_table
populate_position_table ¶
populate_position_table(
table: Table,
data_model: PhotovoltaicPower,
latitude: float,
longitude: float,
elevation: float,
surface_orientation: bool | float = True,
surface_tilt: bool | float = True,
rounding_places: int = 3,
) -> Table
Populate the 'position' table for a photovoltaic module using attributes from the input data model which must contain the positional input parameters of the function described under Parameters :
Returns:
| Name | Type | Description |
|---|---|---|
table | Table | |
Source code in pvgisprototype/cli/print/performance/position.py
def populate_position_table(
table: Table,
data_model: PhotovoltaicPower, # can this become a generic data model ?
latitude: float,
longitude: float,
elevation: float,
surface_orientation: bool | float = True,
surface_tilt: bool | float = True,
rounding_places: int = 3,
) -> Table:
"""
Populate the 'position' table for a photovoltaic module using attributes
from the input data model which must contain the _positional_ input
parameters of the function described under Parameters :
Parameters
----------
- latitude
- longitude
- elevation
- surface_orientation
- surface_tilt
Returns
-------
table: Table
"""
latitude = round_float_values(
latitude, rounding_places
) # rounding_places)
# position_table.add_row(f"{LATITUDE_NAME}", f"[bold]{latitude}[/bold]")
longitude = round_float_values(
longitude, rounding_places
) # rounding_places)
# surface_orientation: float | None = (
# dictionary.get(SURFACE_ORIENTATION_COLUMN_NAME, None)
# if surface_orientation
# else None
# )
# surface_orientation: float = round_float_values(
# surface_orientation, rounding_places
# )
# surface_orientation: float | None = dictionary.get(SURFACE_ORIENTATION_COLUMN_NAME)
if surface_orientation:
surface_orientation = data_model.surface_orientation
if surface_orientation is not None:
surface_orientation = round_float_values(
surface_orientation, rounding_places
)
# Get and round surface_tilt if it's not None
# surface_tilt: float | None = (
# dictionary.get(SURFACE_TILT_COLUMN_NAME, None) if surface_tilt else None
# )
# surface_tilt: float = round_float_values(surface_tilt, rounding_places)
# surface_tilt: float | None = dictionary.get(SURFACE_TILT_COLUMN_NAME)
if surface_tilt:
surface_tilt = data_model.surface_tilt
if surface_tilt is not None:
surface_tilt = round_float_values(surface_tilt, rounding_places)
table.add_row(
f"{latitude}",
f"{longitude}",
f"{elevation}",
f"{surface_orientation}",
f"{surface_tilt}",
f"{data_model.solar_incidence.unit}",
)
# position_table.add_row("Time :", f"{timestamp[0]}")
# position_table.add_row("Time zone :", f"{timezone}")
longest_label_length = max(len(key) for key in data_model.to_dictionary().keys())
surface_position_keys = {
SURFACE_ORIENTATION_NAME,
SURFACE_TILT_NAME,
# ANGLE_UNIT_NAME,
# INCIDENCE_DEFINITION,
# UNIT_NAME,
}
for key, value in data_model.output.items():
if key in surface_position_keys:
padded_key = f"{key} :".ljust(longest_label_length + 3, " ")
if key == INCIDENCE_DEFINITION:
value = f"[yellow]{value}[/yellow]"
table.add_row(padded_key, str(value))
return table
table ¶
Functions:
| Name | Description |
|---|---|
add_table_row | Adds a row to a table with automatic unit handling and optional percentage. |
build_performance_table | Setup the main performance table with appropriate columns. |
add_table_row ¶
add_table_row(
table,
quantity,
value,
unit,
mean_value,
mean_value_unit,
standard_deviation=None,
percentage=None,
reference_quantity=None,
series: ndarray = array([]),
timestamps: DatetimeIndex | Timestamp = now(),
frequency: str = "YE",
source: str | None = None,
quantity_style=None,
value_style: str = "cyan",
unit_style: str = "cyan",
mean_value_style: str = "cyan",
mean_value_unit_style: str = "cyan",
percentage_style: str = "dim",
reference_quantity_style: str = "white",
rounding_places: int = 1,
)
Adds a row to a table with automatic unit handling and optional percentage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table | | required | |
quantity | | required | |
value | | required | |
base_unit | | required | |
percentage | | None | |
reference_quantity | | None | |
rounding_places | int | | 1 |
Notes
- Round value if rounding_places specified.
- Convert units from base_unit to a larger unit if value exceeds 1000.
- Add row to specified table.
Source code in pvgisprototype/cli/print/performance/table.py
def add_table_row(
table,
quantity,
value,
unit,
mean_value,
mean_value_unit,
standard_deviation = None,
percentage = None,
reference_quantity = None,
series: numpy.ndarray = numpy.array([]),
timestamps: DatetimeIndex | Timestamp = Timestamp.now(),
frequency: str = "YE",
source: str | None = None,
quantity_style = None,
value_style: str = "cyan",
unit_style: str = "cyan",
mean_value_style: str = "cyan",
mean_value_unit_style: str = "cyan",
percentage_style: str = "dim",
reference_quantity_style: str = "white",
rounding_places: int = 1,
):
"""
Adds a row to a table with automatic unit handling and optional percentage.
Parameters
----------
table :
The table object to which the row will be added.
quantity :
The name of the quantity being added.
value :
The numerical value associated with the quantity.
base_unit :
The base unit of measurement for the value.
percentage :
Optional; the percentage change or related metric.
reference_quantity :
Optional; the reference quantity for the percentage.
rounding_places :
Optional; the number of decimal places to round the value.
Notes
-----
- Round value if rounding_places specified.
- Convert units from base_unit to a larger unit if value exceeds 1000.
- Add row to specified table.
"""
effects = {
REFLECTIVITY,
SPECTRAL_EFFECT_NAME,
TEMPERATURE_AND_LOW_IRRADIANCE_COLUMN_NAME,
SYSTEM_LOSS,
NET_EFFECT,
}
if value is None or numpy.isnan(value):
signed_value = "-" # this _is_ the variable added in a row !
else:
if isinstance(value, (float, numpy.float32, numpy.float64, int, numpy.int32, numpy.int64)):
styled_value = (
f"[{value_style}]{value:.{rounding_places}f}"
if value_style
else f"{value:.{rounding_places}f}"
)
signed_value = (
f"[{quantity_style}]+{styled_value}"
if quantity in effects and value > 0
else styled_value
)
else:
raise TypeError(f"Unexpected type for value: {type(value)}")
# Need first the unstyled quantity for the `signed_value` :-)
quantity = f"[{quantity_style}]{quantity}" if quantity_style else quantity
# Mean value and unit
mean_value = (
f"[{mean_value_style}]{mean_value:.{rounding_places}f}"
if mean_value_style
else f"{mean_value:.{rounding_places}f}"
)
if standard_deviation:
standard_deviation = (
f"[{mean_value_style}]{standard_deviation:.{rounding_places}f}"
if mean_value_style
else f"{standard_deviation:.{rounding_places}f }"
)
else:
standard_deviation = ""
# Style the unit
unit = f"[{unit_style}]{unit}" if unit_style else unit
# Get the reference quantity
reference_quantity = (
f"[{reference_quantity_style}]{reference_quantity}"
if reference_quantity_style
else reference_quantity
)
# Build the sparkline
sparkline = (
convert_series_to_sparkline(series, timestamps, frequency)
if series.size > 0
else ""
)
# Prepare the basic row data structure
row = [quantity, signed_value, unit]
# Add percentage and reference quantity if applicable
if percentage is not None:
# percentage = f"[red]{percentage:.{rounding_places}f}" if percentage < 0 else f"[{percentage_style}]{percentage:.{rounding_places}f}"
percentage = (
f"[red bold]{percentage:.{rounding_places}f}"
if percentage < 0
else f"[green bold]+{percentage:.{rounding_places}f}"
)
row.extend([f"{percentage}"])
if reference_quantity:
row.extend([reference_quantity])
else:
row.extend([""])
else:
row.extend(["", ""])
if sparkline:
row.extend([sparkline])
if mean_value:
if not sparkline:
row.extend([""])
row.extend([mean_value, mean_value_unit, (standard_deviation)])
else:
row.extend([""])
if source:
row.extend([source])
# table.add_row(
# quantity,
# value,
# unit,
# percentage,
# reference_quantity,
# style=quantity_style
# )
table.add_row(*row)
build_performance_table ¶
build_performance_table(
frequency_label: str,
quantity_style: str,
value_style: str,
unit_style: str,
mean_value_unit_style: str,
percentage_style: str,
) -> Table
Setup the main performance table with appropriate columns.
Source code in pvgisprototype/cli/print/performance/table.py
def build_performance_table(
frequency_label: str,
quantity_style: str,
value_style: str,
unit_style: str,
mean_value_unit_style: str,
percentage_style: str,
# reference_quantity_style,
) -> Table:
"""
Setup the main performance table with appropriate columns.
"""
table = Table(
# title="Photovoltaic Performance",
# caption="Detailed view of changes in photovoltaic performance.",
show_header=True,
header_style="bold magenta",
# show_footer=True,
# row_styles=["none", "dim"],
box=SIMPLE_HEAD,
highlight=True,
)
table.add_column(
"Quantity",
justify="left",
style=quantity_style, # style="magenta",
no_wrap=True,
)
table.add_column(
"Total", # f"{SYMBOL_SUMMATION}",
justify="right",
style=value_style, # style="cyan",
)
table.add_column(
"Unit",
justify="left",
style=unit_style, # style="magenta",
)
table.add_column(
"%",
justify="right",
style=percentage_style, # style="dim",
)
table.add_column(
"of",
justify="left",
style="dim", # style=reference_quantity_style)
)
table.add_column(f"{frequency_label} Sums", style="dim", justify="center")
# table.add_column(f"{frequency_label} Mean", justify="right", style="white dim")#style=value_style)
table.add_column("Mean", justify="right", style="white dim") # style=value_style)
table.add_column(
"Unit", # for Mean values
justify="left",
style=mean_value_unit_style,
)
table.add_column(
"Variability", justify="right", style="dim"
) # New column for standard deviation
table.add_column("Source", style="dim", justify="left")
return table
position ¶
Modules:
| Name | Description |
|---|---|
caption | |
data | |
table | |
caption ¶
Functions:
| Name | Description |
|---|---|
build_solar_position_model_caption | |
build_solar_position_model_caption ¶
build_solar_position_model_caption(
solar_position_model_data,
caption,
timezone,
user_requested_timezone,
surface_orientation=True,
surface_tilt=True,
)
Source code in pvgisprototype/cli/print/position/caption.py
def build_solar_position_model_caption(
solar_position_model_data,
caption,
timezone,
user_requested_timezone,
surface_orientation=True,
surface_tilt=True,
):
"""
"""
# Definitions
## Azimuth origin : North
azimuth_origin = solar_position_model_data.get(SolarPositionParameterMetadataColumnName.azimuth_origin, None)
## Timezone or UTC
## Positions sun-to-horizon : from the set {['Above', 'Low angle', 'Below']}
## for which calculations were performed !
if solar_position_model_data.get(SolarPositionParameterMetadataColumnName.sun_horizon_positions, None):
sun_horizon_positions = [position.value for position in solar_position_model_data.get(SolarPositionParameterMetadataColumnName.sun_horizon_positions, None)]
else:
sun_horizon_positions = None
# Algorithms
## Timing : e.g. NOAA
timing_algorithm = solar_position_model_data.get(SolarPositionParameterColumnName.timing, None)
## Positioning : e.g. NOAA
# solar_positioning_algorithm = get_value_or_default(
solar_positioning_algorithm = solar_position_model_data.get(
# solar_position_model, POSITIONING_ALGORITHM_NAME, None
SolarPositionParameterColumnName.positioning, None
)
adjusted_for_atmospheric_refraction = solar_position_model_data.get('Unrefracted ⌮', None)
## Incidence : e.g. Iqbal
incidence_algorithm = solar_position_model_data.get(
# solar_position_model, INCIDENCE_ALGORITHM_NAME, None
SolarPositionParameterMetadataColumnName.incidence_algorithm, None
)
## Incidence angle : e.g. Sun-Vector-to-Surface-Plane
solar_incidence_definition = None
if incidence_algorithm:
solar_incidence_definition = solar_position_model_data.get(SolarPositionParameterMetadataColumnName.incidence_definition, None)
## Shading : e.g. PVGIS
shading_algorithm = get_value_or_default(
solar_position_model_data, SolarPositionParameterMetadataColumnName.shading_algorithm, None
)
## Shading states : ['all'] -- look corresponding Enum class
# Constants for earth's orbit eccentricity
## Eccentricity Offset : 0.048869
eccentricity_phase_offset = solar_position_model_data.get(
SolarPositionParameterMetadataColumnName.eccentricity_phase_offset, None
)
## Eccentricity Amplitude ⋅⬭ : 0.03344
eccentricity_amplitude = solar_position_model_data.get(
SolarPositionParameterMetadataColumnName.eccentricity_amplitude, None
)
# if solar_position_model.get(SHADING_STATES_COLUMN_NAME) is not None:
# shading_states = [state.value for state in solar_position_model.get(SHADING_STATES_COLUMN_NAME, None)]
# else:
# shading_states = None
# ----------------------------------------------------------------
if surface_orientation or surface_tilt:
caption += "\n[underline]Definitions[/underline] "
if azimuth_origin:
caption += (
f"Azimuth origin : [bold green]{azimuth_origin}[/bold green], "
)
# Fundamental Definitions
if solar_incidence_definition is not None:
caption += f"{SolarPositionParameterMetadataColumnName.incidence_angle.value}: [bold yellow]{solar_incidence_definition}[/bold yellow] "
if sun_horizon_positions:
caption += f"Sun-to-Horizon: [bold]{sun_horizon_positions}[/bold]"
# Algorithms
if timing_algorithm or solar_positioning_algorithm:
caption += "\n[underline]Algorithms[/underline] "
if timing_algorithm:
caption += f"Timing: [bold]{timing_algorithm}[/bold], "
## Timezone is part of the `time_panel`
if solar_positioning_algorithm:
caption += f"Positioning: [bold]{solar_positioning_algorithm}[/bold], "
if adjusted_for_atmospheric_refraction:
# caption += f"\n[underline]Atmospheric Properties[/underline] "
caption += f"Unrefracted zenith: [bold]{adjusted_for_atmospheric_refraction}[/bold], "
if incidence_algorithm:
caption += f"Incidence: [bold yellow]{incidence_algorithm}[/bold yellow], "
if shading_algorithm:
caption += f"Shading: [bold]{shading_algorithm}[/bold]"
# if shading_states: # Not implemented for the position commands !
# caption += f"Shading states : [bold]{shading_states}[/bold]"
if any([eccentricity_phase_offset, eccentricity_amplitude]):
caption += "\n[underline]Constants[/underline] "
if eccentricity_phase_offset and eccentricity_amplitude:
caption += f"{SolarPositionParameterMetadataColumnName.eccentricity_phase_offset_short.value}: {eccentricity_phase_offset}, "
caption += f"{SolarPositionParameterMetadataColumnName.eccentricity_amplitude.value}: {eccentricity_amplitude}, "
return caption.rstrip(", ") # Remove trailing comma + space
data ¶
Functions:
| Name | Description |
|---|---|
print_solar_position_series_in_columns | |
print_solar_position_series_table | |
print_solar_position_table_panels | |
print_solar_position_series_in_columns ¶
print_solar_position_series_in_columns(
longitude,
latitude,
timestamps,
timezone,
table,
rounding_places=ROUNDING_PLACES_DEFAULT,
index: bool = False,
)
Source code in pvgisprototype/cli/print/position/data.py
def print_solar_position_series_in_columns(
longitude,
latitude,
timestamps,
timezone,
table,
rounding_places=ROUNDING_PLACES_DEFAULT,
index: bool = False,
):
""" """
panels = []
# Iterating through each timestamp
for i, timestamp in enumerate(timestamps):
table_panel = Table(title=f"Time: {timestamp}", box=ROUNDED)
table_panel.add_column("Parameter", justify="right")
table_panel.add_column("Value", justify="left")
# Optionally add an index column
if index:
table_panel.add_column("Index", style="dim")
table_panel.add_row("Index", str(i))
# Add longitude, latitude, and other non-time-varying parameters
table_panel.add_row("Longitude", str(longitude))
table_panel.add_row("Latitude", str(latitude))
# For each parameter of interest, aggregate across models for this timestamp
parameters = [
"Declination",
"Hour Angle",
"Zenith",
"Altitude",
"Azimuth",
"Incidence",
]
for param in parameters:
# Assume `table` is a dictionary of models, each containing a list of values for each parameter
values = [
round_float_values(model_result[param][i], rounding_places)
for _, model_result in table.items()
if param in model_result
]
value_str = ", ".join(map(str, values)) # Combine values from all models
table_panel.add_row(param, value_str)
panel = Panel(table_panel, expand=True)
panels.append(panel)
console.print(Columns(panels))
print_solar_position_series_table ¶
print_solar_position_series_table(
longitude,
latitude,
timestamps,
timezone,
table,
position_parameters: Sequence[
SolarPositionParameter
] = all,
title="Solar position overview",
index: bool = False,
version: bool = False,
fingerprint: bool = False,
surface_orientation=None,
surface_tilt=None,
incidence=None,
user_requested_timestamps=None,
user_requested_timezone=None,
rounding_places=ROUNDING_PLACES_DEFAULT,
group_models=False,
panels=False,
) -> None
Source code in pvgisprototype/cli/print/position/data.py
def print_solar_position_series_table(
longitude,
latitude,
timestamps,
timezone,
table,
position_parameters: Sequence[SolarPositionParameter] = SolarPositionParameter.all,
title="Solar position overview",
index: bool = False,
version: bool = False,
fingerprint: bool = False,
surface_orientation=None,
surface_tilt=None,
incidence=None,
user_requested_timestamps=None,
user_requested_timezone=None,
rounding_places=ROUNDING_PLACES_DEFAULT,
group_models=False,
panels=False,
) -> None:
""" """
rounded_table = round_float_values(table, rounding_places)
if panels:
if timestamps.size == 1:
print_solar_position_table_panels(
longitude=longitude,
latitude=latitude,
timestamp=timestamps,
timezone=timezone,
solar_position_table=rounded_table,
position_parameters=position_parameters,
rounding_places=rounding_places,
user_requested_timestamp=user_requested_timestamps,
user_requested_timezone=user_requested_timezone,
)
else:
longitude = round_float_values(longitude, rounding_places)
latitude = round_float_values(latitude, rounding_places)
# Build the main caption
caption = build_caption(
data_dictionary=rounded_table,
longitude=longitude,
latitude=latitude,
# elevation=elevation,
rounding_places=rounding_places,
surface_orientation=True,
surface_tilt=True,
)
# Iterate over multiple solar position models -- we _can_ have many !
for _, model_result in rounded_table.items():
if model_result:
model_result = flatten_dictionary(model_result)
# Update the caption with model-specific metadata
model_caption = build_solar_position_model_caption(
solar_position_model_data=model_result,
caption=caption,
timezone=timezone,
user_requested_timezone=user_requested_timezone,
)
# then : Create a Legend table for the symbols in question
legend = build_legend_table(
dictionary=model_result,
caption=model_caption,
show_header=False,
box=None,
)
# Time might be Local
if user_requested_timestamps is not None:
time_column_name = LOCAL_TIME_COLUMN_NAME
else:
time_column_name = TIME_COLUMN_NAME
if timestamps is not None:
if user_requested_timezone is not None:
if user_requested_timezone != ZoneInfo("UTC"):
time_column_name = LOCAL_TIME_COLUMN_NAME
timezone_string = f"Local Zone: [bold]{timezone}[/bold]"
else:
time_column_name = TIME_COLUMN_NAME
if timezone:
if timezone == ZoneInfo('UTC'):
timezone_string = f"[bold]{timezone}[/bold]"
else:
timezone_string = f"Local Zone: [bold]{timezone}[/bold]"
# Build solar position table structure
solar_position_table = build_solar_position_table(
title=title,
index=index,
input_table=rounded_table,
dictionary=model_result,
position_parameters=position_parameters,
# timestamps=timestamps,
# rounding_places=rounding_places,
time_column_name=time_column_name,
time_column_footer=f"{SYMBOL_SUMMATION} / [blue]{SYMBOL_MEAN}[/blue]", # Abusing this "cell" as a "Row Name"
time_column_footer_style="purple", # to make it somehow distinct from the Column !
# keys_to_sum = KEYS_TO_SUM,
# keys_to_average = KEYS_TO_AVERAGE,
# keys_to_exclude = KEYS_TO_EXCLUDE,
)
# -------------------------------------------------- Ugly Hack ---
if user_requested_timestamps is not None:
timestamps = user_requested_timestamps
# --- Ugly Hack --------------------------------------------------
output_table = populate_solar_position_table(
table=solar_position_table,
model_result=model_result,
timestamps=timestamps,
index=index,
rounding_places=rounding_places,
)
# Create Panels for time, caption and legend
time_table = build_time_table()
frequency, frequency_label = infer_frequency_from_timestamps(timestamps)
time_table.add_row(
str(timestamps.strftime("%Y-%m-%d %H:%M").values[0]),
str(frequency) if frequency and frequency != "Single" else "-",
str(timestamps.strftime("%Y-%m-%d %H:%M").values[-1]),
str(timezone_string),
)
time_panel = build_time_panel(time_table, padding=(0, 1, 2, 1))
# Version & Fingerprint
fingerprint = retrieve_fingerprint(dictionary=model_result)
version_and_fingerprint_and_column = (
build_version_and_fingerprint_columns(
version=version,
fingerprint=fingerprint,
)
)
# if verbose: # Print if requested via at least 1x `-v` ?
print_solar_position_table_and_metadata_panels(
time=time_panel,
caption=model_caption,
table=output_table,
legend=legend,
fingerprint=version_and_fingerprint_and_column,
)
print_solar_position_table_panels ¶
print_solar_position_table_panels(
longitude,
latitude,
timestamp,
timezone,
solar_position_table,
rounding_places=ROUNDING_PLACES_DEFAULT,
position_parameters=all,
surface_orientation=True,
surface_tilt=True,
user_requested_timestamp=None,
user_requested_timezone=None,
) -> None
Source code in pvgisprototype/cli/print/position/data.py
def print_solar_position_table_panels(
longitude,
latitude,
timestamp,
timezone,
solar_position_table,
rounding_places=ROUNDING_PLACES_DEFAULT,
position_parameters=SolarPositionParameter.all,
surface_orientation=True,
surface_tilt=True,
user_requested_timestamp=None,
user_requested_timezone=None,
) -> None:
"""
"""
first_model = solar_position_table[next(iter(solar_position_table))]
panels = []
# surface position Panel
table = Table(box=None, show_header=False, show_edge=False, pad_edge=False)
table.add_column(justify="right", style="none", no_wrap=True)
table.add_column(justify="left")
table.add_row(f"{LATITUDE_COLUMN_NAME} :", f"[bold]{latitude}[/bold]")
table.add_row(f"{LONGITUDE_COLUMN_NAME} :", f"[bold]{longitude}[/bold]")
table.add_row("Time :", f"{timestamp[0]}")
table.add_row("Time zone :", f"{timezone}")
longest_label_length = max(len(key) for key in first_model.keys())
surface_position_keys = {
SURFACE_ORIENTATION_NAME,
SURFACE_TILT_NAME,
ANGLE_UNIT_NAME,
INCIDENCE_DEFINITION,
UNIT_NAME,
}
for key, value in first_model.items():
if key in surface_position_keys:
padded_key = f"{key} :".ljust(longest_label_length + 3, " ")
if key == INCIDENCE_DEFINITION:
value = f"[yellow]{value}[/yellow]"
table.add_row(padded_key, str(value))
position_panel = Panel(
table,
title="Surface Position",
box=HORIZONTALS,
style="",
expand=False,
padding=(0, 2),
)
panels.append(position_panel)
# solar position Panel/s
for model_result in solar_position_table.values():
table = Table(box=None, show_header=False, show_edge=False, pad_edge=False)
table.add_column(justify="right", style="none", no_wrap=True)
table.add_column(justify="left")
longest_label_length = max(len(key) for key in model_result.keys())
_index = 0
position_parameter_values = {
SolarPositionParameter.declination: lambda idx=_index: get_scalar(
get_value_or_default(model_result, DECLINATION_COLUMN_NAME),
idx,
rounding_places,
),
# SolarPositionParameter.timing: lambda idx=_index: str(get_value_or_default(
# model_result, TIME_ALGORITHM_NAME
# )),
# SolarPositionParameter.positioning: lambda idx=_index: str(get_value_or_default(
# model_result, POSITIONING_ALGORITHM_NAME
# )),
SolarPositionParameter.hour_angle: lambda idx=_index: get_scalar(
get_value_or_default(model_result, HOUR_ANGLE_COLUMN_NAME),
idx,
rounding_places,
),
SolarPositionParameter.zenith: lambda idx=_index: get_scalar(
get_value_or_default(model_result, ZENITH_COLUMN_NAME),
idx,
rounding_places,
),
SolarPositionParameter.altitude: lambda idx=_index: get_scalar(
get_value_or_default(model_result, ALTITUDE_COLUMN_NAME, None),
idx,
rounding_places,
),
SolarPositionParameter.azimuth: lambda idx=_index: get_scalar(
get_value_or_default(model_result, AZIMUTH_COLUMN_NAME),
idx,
rounding_places,
),
SolarPositionParameter.incidence: lambda idx=_index: get_scalar(
get_value_or_default(model_result, INCIDENCE_COLUMN_NAME),
idx,
rounding_places,
),
SolarPositionParameter.event_time: lambda idx=_index: get_scalar(
get_value_or_default(model_result, SOLAR_EVENT_TIME_COLUMN_NAME),
idx,
rounding_places,
),
SolarPositionParameter.event_type: lambda idx=_index: get_scalar(
get_value_or_default(model_result, SOLAR_EVENT_COLUMN_NAME),
idx,
rounding_places,
),
}
for parameter in position_parameters:
if parameter in position_parameter_values:
padded_key = f"{parameter.value} :".ljust(longest_label_length + 1, " ")
value = position_parameter_values[parameter]()
if parameter == AZIMUTH_ORIGIN_NAME:
value = f"[yellow]{value}[/yellow]"
table.add_row(padded_key, str(value))
title = f"[bold]{get_value_or_default(model_result, POSITIONING_ALGORITHM_NAME)}[/bold]"
panel = Panel(
table,
title=title,
box=ROUNDED,
# style=panel_style,
padding=(0, 2),
)
panels.append(panel)
columns = Columns(panels, expand=True, equal=True, padding=2)
console.print(columns)
table ¶
Functions:
| Name | Description |
|---|---|
build_solar_position_table | Notes |
populate_solar_position_table | Populates a Rich Table with solar position data |
print_solar_position_table_and_metadata_panels | |
build_solar_position_table ¶
build_solar_position_table(
title: str,
index: bool,
input_table: dict,
dictionary: dict,
position_parameters: Sequence[SolarPositionParameter],
time_column_name: str,
time_column_footer: RenderableType = SYMBOL_SUMMATION,
time_column_footer_style: str = "purple",
) -> Table
Notes
The input dictionary is a flat structure !
Source code in pvgisprototype/cli/print/position/table.py
def build_solar_position_table(
title: str,
index: bool,
input_table: dict,
dictionary: dict,
position_parameters: Sequence[SolarPositionParameter],
# timestamps,
# rounding_places: int,
time_column_name: str,
time_column_footer: RenderableType = SYMBOL_SUMMATION,
time_column_footer_style: str = "purple",
# keys_to_sum = KEYS_TO_SUM,
# keys_to_average = KEYS_TO_AVERAGE,
# keys_to_exclude = KEYS_TO_EXCLUDE,
) -> Table:
"""
Notes
-----
The input `dictionary` is a flat structure !
"""
from rich.table import Table
from rich.box import SIMPLE_HEAD
table = Table(
title=title,
caption_justify="left",
expand=False,
padding=(0, 1),
box=SIMPLE_HEAD,
show_footer=True,
show_header=True,
header_style="bold", # "bold gray50",
row_styles=["none", "dim"],
highlight=True, # To light or Not ?
)
# base columns
if index:
table.add_column("Index")
## Time column
table.add_column(
time_column_name,
justify="left",
no_wrap=True,
footer=time_column_footer,
footer_style=time_column_footer_style,
)
# remove the 'Title' entry! ---------------------------------------------
dictionary.pop("Title", NOT_AVAILABLE)
# ------------------------------------------------------------- Important
# Get the data
first_model = input_table[next(iter(input_table))]
# `first_model` contains the "data" in case of a single `position` command
# i.e. `position azimuth`
# In case of the `position overview` command, however :
# Pull out the two relevant nested dicts in case of the overview command !?
core_data = first_model.get("Core", {})
events_data = first_model.get("Solar Events", {})
# For each requested parameter, derive its header and find its data
for parameter in position_parameters:
# Skip enum members without a matching ColumnName
if parameter.name not in SolarPositionParameterColumnName.__members__ \
or parameter.name in ("timing", "positioning"):
continue
# Get the human-readable header from the ColumnName enum
header = SolarPositionParameterColumnName[parameter.name].value
value = find_nested_value(first_model, header)
if value is None:
if header in core_data:
value = find_nested_value(core_data, header)
elif header in events_data:
value = find_nested_value(events_data, header)
else:
continue # not present
from numpy import datetime64, isnat
if parameter in (
SolarPositionParameter.event_type,
SolarPositionParameter.event_time,
):
# If value is None, there is no event array at all—skip
if value is None:
continue
# For event_time: dtype datetime64, for event_type: object dtype/SolarEvent
# If value is not an array, make it a list so iteration succeeds
if not hasattr(value, "__iter__") or isinstance(value, str):
value_list = [value]
else:
value_list = value
def is_real_event(ev):
# For datetime columns (event_time)
if isinstance(ev, datetime64):
return not isnat(ev)
# For event_type columns
if ev is not None and hasattr(ev, "name") and ev.name == "none":
return False
return ev not in (None, "None")
has_data = any(is_real_event(v) for v in value_list)
if not has_data:
continue # skip entirely
table.add_column(header)
return table
populate_solar_position_table ¶
populate_solar_position_table(
table: Table,
model_result: dict,
timestamps,
index: bool,
rounding_places: int,
sparkline: bool = False,
)
Populates a Rich Table with solar position data using the already-built table structure (columns). Compatible with flattened model_result dictionaries.
Source code in pvgisprototype/cli/print/position/table.py
def populate_solar_position_table(
table: Table,
model_result: dict,
timestamps,
index: bool,
rounding_places: int,
# position_parameters: Sequence[SolarPositionParameter],
sparkline: bool = False,
):
"""
Populates a Rich Table with solar position data
using the already-built table structure (columns).
Compatible with flattened model_result dictionaries.
"""
from numpy import datetime64, bool_
for idx, timestamp in enumerate(timestamps):
row = []
if index:
row.append(str(idx + 1))
row.append(to_datetime(timestamp).strftime("%Y-%m-%d %H:%M:%S"))
# Iterate over each table column (already reflecting the final structure)
for column in table.columns:
header = column.header
if header in ("Index", "Time"):
continue
# Retrieve matching array from model_result
value_array = model_result.get(header)
if value_array is None or len(value_array) <= idx:
row.append("")
continue
value = get_scalar(value_array, idx, rounding_places)
# Format final cell output
if value is None:
row.append("")
elif isinstance(value, SolarEvent):
row.append(format_string(value.value))
elif isinstance(value, str):
vl = value.lower()
style = {
"above": "bold yellow",
"low angle": "dark_orange",
"below": "red",
}.get(vl)
row.append(Text(value, style=style) if style else value)
elif isinstance(value, datetime64):
if isna(value):
row.append("")
else:
dt = value.astype("datetime64[s]").astype("O")
row.append(str(dt.time()))
elif isinstance(value, bool_) or isinstance(value, bool):
# bool _must_ come before numeric !
row.append(str(bool(value)))
elif isinstance(value, (int, float, numpy.generic)):
rounded = str(round_float_values(value, rounding_places))
style = "bold cyan"
if value < 0:
style = "bold red"
elif value == 0:
style = "highlight dim"
row.append(Text(rounded, style=style))
else:
row.append(str(value))
table.add_row(*row)
if sparkline:
# Build a footer row of sparklines (or blank) for each column
footer_cells = []
for column in table.columns:
header = column.header
# Keep index/time columns blank
if header in ("Index", "Time"):
# footer_cells.append("")
footer_cells.append("Sparkline")
continue
# Retrieve the full series for this column
series = model_result.get(header)
if series is None or len(series) == 0:
footer_cells.append("")
continue
# Only numeric series get sparklines
# Skip booleans, strings, events, datetimes
if not isinstance(series, (list, tuple)) and hasattr(series, "dtype"):
dtype = series.dtype
else:
footer_cells.append("")
continue
if dtype.kind in ("i", "u", "f"):
# Generate sparkline: pass the raw numpy array and timestamps
from pvgisprototype.cli.print.sparklines import (
convert_series_to_sparkline,
)
spark = convert_series_to_sparkline(
series=series,
timestamps=timestamps,
frequency=timestamps.freq,
)
footer_cells.append(Text(spark, style="dim"))
else:
footer_cells.append("")
# Add the sparkline footer as a final row
table.add_row(*footer_cells)
return table
print_solar_position_table_and_metadata_panels ¶
print_solar_position_table_and_metadata_panels(
time,
caption,
table,
legend,
fingerprint,
subtitle_caption="Reference",
subtitle_legend="Legend",
)
Source code in pvgisprototype/cli/print/position/table.py
def print_solar_position_table_and_metadata_panels(
time,
caption,
table,
legend,
fingerprint,
subtitle_caption="Reference",
subtitle_legend="Legend",
):
""" """
console = Console()
console.print(table)
panels = []
if time:
panels.append(
time,
)
if caption:
panels.append(
Panel(
caption,
subtitle=f"[gray]{subtitle_caption}[/gray]",
subtitle_align="right",
border_style="dim",
expand=False,
)
)
if legend:
panels.append(
Panel(
legend,
subtitle=f"[dim]{subtitle_legend}[/dim]",
subtitle_align="right",
border_style="dim",
# style="dim",
expand=False,
padding=(0, 1),
)
)
if panels:
console.print(Columns(panels))
if fingerprint: # version & fingerprint
console.print(fingerprint)
qr ¶
Functions:
| Name | Description |
|---|---|
print_quick_response_code | |
print_quick_response_code ¶
print_quick_response_code(
dictionary: dict,
longitude: float,
latitude: float,
elevation: float | None = None,
surface_orientation: bool = True,
surface_tilt: bool = True,
timestamps: DatetimeIndex = DatetimeIndex([now()]),
rounding_places: int = ROUNDING_PLACES_DEFAULT,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
optimal_surface_position: bool = False,
output_type: QuickResponseCode = Base64,
) -> None
Source code in pvgisprototype/cli/print/qr.py
def print_quick_response_code(
dictionary: dict,
longitude: float,
latitude: float,
elevation: float | None = None,
surface_orientation: bool = True,
surface_tilt: bool = True,
timestamps: DatetimeIndex = DatetimeIndex([datetime.now()]),
rounding_places: int = ROUNDING_PLACES_DEFAULT,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
optimal_surface_position: bool = False,
output_type: QuickResponseCode = QuickResponseCode.Base64,
) -> None:
""" """
if optimal_surface_position:
quick_response_code = generate_quick_response_code_optimal_surface_position(
dictionary=dictionary,
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=True,
surface_tilt=True,
timestamps=timestamps,
rounding_places=ROUNDING_PLACES_DEFAULT,
output_type=output_type,
)
else:
quick_response_code = generate_quick_response_code(
dictionary=dictionary,
longitude=longitude,
latitude=latitude,
elevation=elevation,
surface_orientation=True,
surface_tilt=True,
timestamps=timestamps,
rounding_places=ROUNDING_PLACES_DEFAULT,
output_type=output_type,
)
if output_type.value == QuickResponseCode.Base64:
print(quick_response_code)
if output_type.value == QuickResponseCode.Image:
quick_response_code.print_ascii()
quantity ¶
Functions:
| Name | Description |
|---|---|
print_quantity_table | |
print_quantity_table ¶
print_quantity_table(
dictionary: dict = dict(),
title: str = "Series",
main_key: ndarray | None = None,
rounding_places: int = ROUNDING_PLACES_DEFAULT,
verbose=1,
index: bool = False,
) -> None
Source code in pvgisprototype/cli/print/quantity.py
def print_quantity_table(
dictionary: dict = dict(),
title: str = "Series",
main_key: ndarray | None = None,
rounding_places: int = ROUNDING_PLACES_DEFAULT,
verbose=1,
index: bool = False,
) -> None:
"""
"""
# Hacky
dictionary = dictionary.to_dictionary() # custom data model method !
table = Table(title=title, box=SIMPLE_HEAD)
if index:
table.add_column("Index")
# remove the 'Title' entry! ---------------------------------------------
# dictionary.pop(TITLE_KEY_NAME, NOT_AVAILABLE)
# ------------------------------------------------------------- Important
# # base columns
# if verbose > 0:
# additional columns based dictionary keys
for key in dictionary.keys():
if dictionary[key] is not None:
table.add_column(key)
if main_key is not None: # consider the 1st key of having the "valid" number of values
# main_key = list(dictionary.keys())[0]
main_key = dictionary['value'] # Hacky, yet we know it !
# Convert single float or int values to arrays of the same length as the "main' key
for key, value in dictionary.items():
if isinstance(value, (float, int)):
dictionary[key] = full(main_key.shape, value)
if isinstance(value, str):
dictionary[key] = full(main_key.shape, str(value))
# Zip series
zipped_series = zip(*dictionary.values())
# Populate table
index_counter = 1
for values in zipped_series:
row = []
if index:
row.append(str(index_counter))
index_counter += 1
for idx, (column_name, value) in enumerate(zip(dictionary.keys(), values)):
if idx == 0: # assuming after 'Time' is the value of main interest
bold_value = Text(
str(round_float_values(value, rounding_places)), style="bold"
)
row.append(bold_value)
else:
if not isinstance(value, str):
if SYMBOL_LOSS in column_name:
red_value = Text(
str(round_float_values(value, rounding_places)),
style="bold red",
)
row.append(red_value)
else:
row.append(str(round_float_values(value, rounding_places)))
else:
row.append(value)
table.add_row(*row)
if verbose:
Console().print(table)
series ¶
Functions:
| Name | Description |
|---|---|
print_series_statistics | |
print_series_statistics ¶
print_series_statistics(
data_array: ndarray,
timestamps: DatetimeIndex,
title: str = "Time series",
groupby: str | None = None,
monthly_overview: bool = False,
rounding_places: int | None = None,
verbose=VERBOSE_LEVEL_DEFAULT,
) -> None
Source code in pvgisprototype/cli/print/series.py
def print_series_statistics(
data_array: numpy.ndarray,
timestamps: DatetimeIndex,
title: str = "Time series",
groupby: str | None = None,
monthly_overview: bool = False,
rounding_places: int | None = None,
verbose = VERBOSE_LEVEL_DEFAULT,
) -> None:
"""
"""
rename_monthly_output_rows = {
"Sum of Group Means": "Yearly PV energy",
"Sum of Global Inclined Irradiance": "Yearly in-plane irradiance",
}
if groupby and groupby.lower() == "monthly":
monthly_overview = True
table = Table(
title=title,
caption="Caption text",
show_header=True,
header_style="bold magenta",
row_styles=["none", "dim"],
box=SIMPLE_HEAD,
highlight=True,
)
# Compute statistics
if monthly_overview: # typical overview !
table.add_column(
"Statistic", justify="right", style="bright_blue", no_wrap=True
)
table.add_column("Value", style="cyan")
# get monthly mean values
statistics = calculate_series_statistics(data_array, timestamps, "M")
else:
table.add_column("Statistic", justify="right", style="magenta", no_wrap=True)
table.add_column("Value", style="cyan")
statistics = calculate_series_statistics(data_array, timestamps, groupby)
# Basic Metadata
basic_metadata = ["Start", "End", "Count"] if not monthly_overview else ["Start", "End"]
for key in basic_metadata:
if key in statistics:
add_statistic_row(table, key, statistics[key])
table.add_row("", "") # --%<--- Separate ----
# grouping_labels = {
# 'Y': 'Yearly means',
# 'S': 'Seasonal means',
# 'M': 'Monthly means',
# 'W': 'Weekly means',
# 'D': 'Daily means',
# 'H': 'Hourly means',
# }
# Groups by
time_groupings = [
"Yearly means",
"Seasonal means",
"Monthly means",
"Weekly means",
"Daily means",
"Hourly means",
f"{groupby} means" if groupby else None, # do not print custom frequency means
]
if monthly_overview:
monthly_metadata = [
# 'Min',
# 'Mean',
# 'Max',
# 'Standard deviation',
"Sum of Group Means",
"Irradiance",
f"Sum of Global Inclined Irradiance",
]
# Add statistics
for key, value in statistics.items():
if not monthly_overview:
if (
key not in basic_metadata
and key not in time_groupings
):
add_statistic_row(table, key, value, rounding_places)
else:
if (
key in monthly_metadata
and key not in basic_metadata
):
display_key = rename_monthly_output_rows.get(key, key)
add_statistic_row(table, display_key, value, rounding_places)
table.add_row("", "") # --%<--- Separate --------------------------------
# Process Groups with Helper
custom_frequency_label = (
f"{groupby} means" if groupby and groupby not in time_groupings else None
)
for group in time_groupings:
if group in statistics:
group_rows = format_group_statistics(statistics, group)
for name, value in group_rows:
add_statistic_row(table, name, value)
# Custom frequency group
if custom_frequency_label and custom_frequency_label in statistics:
custom_freq_data = statistics[custom_frequency_label]
# ----------------------------------------------------- the Old way --
# period_count = 1
# for value in custom_freq_data:
# label = f"{groupby} Period {period_count}"
# table.add_row(label, str(value))
# period_count += 1
# table.add_row("", "")
# ----------------------------------------------------- the Old way --
for period_count, value in enumerate(custom_freq_data, start=1):
add_statistic_row(
table=table,
key=f"{groupby} Period {period_count}",
value=value,
rounding_places=rounding_places,
)
# Index of items
index_metadata = (
[
"Time of Min",
"Index of Min",
"Time of Max",
"Index of Max",
]
if not monthly_overview
else []
)
# # The old way ! --------------------------------------------------------
# for key, value in statistics.items():
# if key in index_metadata:
# # table.add_row(key, str(round_float_values(value, rounding_places)))
# table.add_row(key, str(value))
# # The old way ! --------------------------------------------------------
for key in index_metadata:
if key in statistics:
add_statistic_row(
table=table,
key=key,
value=statistics[key],
)
# Create and display Panel with Table
panel = Panel(table, title="Statistics", expand=False)
console = Console()
console.print(panel)
solar_time ¶
Functions:
| Name | Description |
|---|---|
print_solar_time_series_table | Print the solar time series results in a table format, with repeated values (like min/max time, unit) |
print_solar_time_series_table ¶
print_solar_time_series_table(
longitude,
timestamps,
timezone,
solar_time_series,
title="True Solar Time",
index: bool = False,
rounding_places=ROUNDING_PLACES_DEFAULT,
user_requested_timestamps=None,
user_requested_timezone=None,
group_models=False,
) -> None
Print the solar time series results in a table format, with repeated values (like min/max time, unit) placed in the caption.
Source code in pvgisprototype/cli/print/solar_time.py
def print_solar_time_series_table(
longitude,
timestamps,
timezone,
solar_time_series,
title="True Solar Time",
index: bool = False,
rounding_places=ROUNDING_PLACES_DEFAULT,
user_requested_timestamps=None,
user_requested_timezone=None,
group_models=False,
) -> None:
"""
Print the solar time series results in a table format, with repeated values (like min/max time, unit)
placed in the caption.
"""
# Round longitude for consistent display
longitude = round_float_values(longitude, rounding_places)
rounded_solar_time_series = round_float_values(solar_time_series, rounding_places)
# Define columns for the table
columns = []
if index:
columns.append("Index")
# Define the time column name based on the timezone or user request
time_column_name = TIME_COLUMN_NAME if user_requested_timestamps is None else LOCAL_TIME_COLUMN_NAME
columns.append(time_column_name)
columns.append("Solar Time")
# Extract metadata that will be placed in the caption
caption = build_caption(
longitude=longitude,
latitude=None,
rounded_table=rounded_solar_time_series,
timezone=timezone,
user_requested_timezone=user_requested_timezone,
)
for model_name, model_result in rounded_solar_time_series.items():
model_caption = caption
model_caption += f"\n Timing Model : [bold]{model_name}[/bold]"
# Extract metadata for the caption
from pvgisprototype.cli.print.helpers import get_value_or_default
true_solar_time = model_result.get(SOLAR_TIME_NAME, {})
if isinstance(true_solar_time, TrueSolarTime):
solar_timing_algorithm = true_solar_time.timing_algorithm
unit = true_solar_time.unit
min_time = true_solar_time.min_minutes
max_time = true_solar_time.max_minutes
caption = build_caption(
longitude=longitude,
latitude=None,
rounded_table=rounded_solar_time_series,
timezone=timezone,
user_requested_timezone=user_requested_timezone,
minimum_value=min_time,
maximum_value=max_time,
)
# Create the table object
table_obj = Table(
*columns,
title=title,
box=SIMPLE_HEAD,
show_header=True,
header_style="bold magenta",
)
# Iterate over timestamps and add rows for each timestamp with the corresponding solar time
for _index, timestamp in enumerate(timestamps):
row = []
if index:
row.append(str(_index + 1)) # Add index if requested
row.append(str(timestamp)) # Add timestamp
# Extract solar time values for the current timestamp
solar_time_values = true_solar_time.value
solar_time_value = (
f'{solar_time_values[_index]:.{rounding_places}f}' if _index < len(solar_time_values) else NOT_AVAILABLE
)
# row.append(model_name) # Add model name
row.append(solar_time_value) # Add solar time for the given timestamp
table_obj.add_row(*row)
# Print the table and caption
Console().print(table_obj)
Console().print(Panel(model_caption, expand=False))
sparklines ¶
Functions:
| Name | Description |
|---|---|
convert_series_to_sparkline | |
convert_series_to_sparkline ¶
convert_series_to_sparkline(
series: NDArray,
timestamps: DatetimeIndex | Timestamp,
frequency: str,
)
Source code in pvgisprototype/cli/print/sparklines.py
def convert_series_to_sparkline(
series: NDArray,
timestamps: DatetimeIndex | Timestamp,
frequency: str,
):
""" """
pandas_series = Series(series, timestamps)
if (
frequency == "SINGLE"
or (isinstance(timestamps, DatetimeIndex) and timestamps.size == 1)
or isinstance(timestamps, Timestamp)
):
return "▁" # Return a flat line for a single value
yearly_sum_series = pandas_series.resample(frequency).sum()
maximum=None
if yearly_sum_series.all() == 0:
maximum=1
sparkline = sparklines(yearly_sum_series, maximum=maximum)[0]
return sparkline
spectral_factor ¶
Functions:
| Name | Description |
|---|---|
print_spectral_factor | Print the spectral factor series in a formatted table. |
print_spectral_factor_statistics | Print the spectral factor statistics in a formatted table. |
print_spectral_factor ¶
print_spectral_factor(
timestamps,
spectral_factor_container: Dict,
spectral_factor_model: List,
photovoltaic_module_type: List,
rounding_places: int = 3,
include_statistics: bool = False,
title: str = "Spectral Factor",
verbose: int = 1,
index: bool = False,
show_footer: bool = True,
) -> None
Print the spectral factor series in a formatted table.
Source code in pvgisprototype/cli/print/spectral_factor.py
def print_spectral_factor(
timestamps,
spectral_factor_container: Dict,
spectral_factor_model: List,
photovoltaic_module_type: List,
rounding_places: int = 3,
include_statistics: bool = False,
title: str = "Spectral Factor",
verbose: int = 1,
index: bool = False,
show_footer: bool = True,
) -> None:
"""Print the spectral factor series in a formatted table.
Parameters
----------
- timestamps :
The time series timestamps.
- spectral_factor :
Dictionary containing spectral factor data for different models and module types.
- spectral_factor_model :
List of spectral factor models.
- photovoltaic_module_type :
List of photovoltaic module types.
- rounding_places :
Number of decimal places for rounding.
- include_statistics :
Whether to include mean, median, etc., in the output.
- verbose : int
Verbosity level.
- index : bool
Whether to show an index column.
"""
# Initialize the table with title and formatting options
table = Table(
title=title,
caption_justify="left",
expand=False,
padding=(0, 1),
box=SIMPLE_HEAD,
show_footer=show_footer,
)
if index:
table.add_column("Index")
table.add_column("Time", footer="μ" if show_footer else None)
# Initialize dictionary to store the means for each module type
means = {}
# Calculate mean values for the footer
if show_footer:
for module_type in photovoltaic_module_type:
model = spectral_factor_model[0] # Assuming only one model for simplicity
spectral_factor_series = spectral_factor_container.get(model).get(module_type).get(SPECTRAL_FACTOR_COLUMN_NAME)
mean_value = numpy.nanmean(spectral_factor_series)
means[module_type.value] = f"{mean_value:.{rounding_places}f}"
# Add columns for each photovoltaic module type with optional footer
for module_type in photovoltaic_module_type:
footer_text = means.get(module_type.value, "") if show_footer else None
table.add_column(f"{module_type.value}", justify="right", footer=footer_text)
# Aggregate data for each timestamp
for _index, timestamp in enumerate(timestamps):
row = []
if index:
row.append(str(_index + 1)) # count from 1
row.append(str(timestamp))
for module_type in photovoltaic_module_type:
model = spectral_factor_model[0] # Assuming only one model for simplicity
sm_value = spectral_factor_container.get(model).get(module_type).get(SPECTRAL_FACTOR_COLUMN_NAME)[_index]
row.append(f"{round(sm_value, rounding_places):.{rounding_places}f}")
table.add_row(*row)
print()
# Print the table if verbose is enabled
if verbose:
console = Console()
console.print(table)
# Optionally, display additional information in a panel
if verbose > 1:
extra_info = "Spectral Mismatch calculated for different photovoltaic module types using specified models."
console.print(Panel(extra_info, expand=False))
print_spectral_factor_statistics ¶
print_spectral_factor_statistics(
spectral_factor: Dict,
spectral_factor_model: List,
photovoltaic_module_type: List,
timestamps: DatetimeIndex,
groupby: str | None = None,
title: str = "Spectral Mismatch Statistics",
rounding_places: int = 3,
verbose: int = 1,
show_footer: bool = True,
monthly_overview: bool = False,
) -> None
Print the spectral factor statistics in a formatted table.
Source code in pvgisprototype/cli/print/spectral_factor.py
def print_spectral_factor_statistics(
spectral_factor: Dict,
spectral_factor_model: List,
photovoltaic_module_type: List,
timestamps: DatetimeIndex,
groupby: str | None = None,
title: str = "Spectral Mismatch Statistics",
rounding_places: int = 3,
verbose: int = 1,
show_footer: bool = True,
monthly_overview: bool = False,
) -> None:
"""
Print the spectral factor statistics in a formatted table.
"""
rename_monthly_output_rows = {
"Sum of Group Means": "Yearly PV energy",
f"Sum of {GLOBAL_INCLINED_IRRADIANCE_COLUMN_NAME}": "Yearly in-plane irradiance",
}
# Iterate through spectral factor models
for model in spectral_factor_model:
if model.value not in spectral_factor:
print(f"Spectral factor model {model.value} not found in statistics.")
continue
# Create a new table for this model
table = Table(
title=f"{title} ({model.value})",
caption="Spectral Factor Statistics",
show_header=True,
header_style="bold magenta",
row_styles=["none", "dim"],
box=SIMPLE_HEAD,
highlight=True,
)
# Add a column for each photovoltaic module type
table.add_column(
"Statistic", justify="right", style="bright_blue", no_wrap=True
)
for module_type in photovoltaic_module_type:
table.add_column(f"{module_type.value}",
# justify="right",
style="cyan")
# Calculate statistics for each module type
statistics = calculate_spectral_factor_statistics(
spectral_factor, spectral_factor_model, photovoltaic_module_type, timestamps, rounding_places, groupby
)
# Basic metadata (Start, End, Count)
basic_metadata = ["Start", "End", "Count"]
for stat_name in basic_metadata:
row = [stat_name]
for module_type in photovoltaic_module_type:
try:
value = statistics[model.value][module_type.value].get(stat_name, "N/A")
rounded_value = f"{round_float_values(value, rounding_places)}"
except KeyError:
rounded_value = "N/A"
row.append(rounded_value)
table.add_row(*row)
# Separate!
table.add_row("", "")
# Extended statistics (Min, Mean, Max, Sum, etc.)
extended_statistics = ["Min", "Mean", "Max", "Sum", "25th Percentile", "Median", "Mode", "Variance", "Standard deviation"]
for stat_name in extended_statistics:
row = [stat_name]
for module_type in photovoltaic_module_type:
try:
value = statistics[model.value][module_type.value].get(stat_name, "N/A")
rounded_value = f"{round_float_values(value, rounding_places)}"
except KeyError:
rounded_value = "N/A"
row.append(rounded_value)
table.add_row(*row)
# Separate!
table.add_row("", "")
# Add index statistics (Time of Min, Index of Min, Time of Max, Index of Max)
index_metadata = ["Time of Min", "Index of Min", "Time of Max", "Index of Max"]
for stat_name in index_metadata:
row = [stat_name]
for module_type in photovoltaic_module_type:
try:
value = statistics[model.value][module_type.value].get(stat_name, "N/A")
rounded_value = f"{round_float_values(value, rounding_places)}"
except KeyError:
rounded_value = "N/A"
row.append(rounded_value)
table.add_row(*row)
# Add a separating row after the statistics for clarity
table.add_row("", "")
# Groupings (Yearly, Monthly, Custom Frequency)
time_groupings = [
"Yearly means",
"Monthly means",
"Weekly means",
"Daily means",
"Hourly means",
]
custom_freq_label = f"{groupby} means" if groupby and groupby not in time_groupings else None
if custom_freq_label and custom_freq_label in statistics:
custom_freq_data = statistics[custom_freq_label]
period_count = 1
for val in custom_freq_data:
label = f"{groupby} Period {period_count}"
table.add_row(label, str(val))
period_count += 1
table.add_row("", "")
# Optionally add footer if show_footer is True
if show_footer:
table.add_row("", "")
table.add_row("Summary", "Footer with additional info")
# Print the table for this model
if verbose:
console = Console()
console.print(table)
surface ¶
Functions:
| Name | Description |
|---|---|
build_surface_position_table | |
print_optimal_surface_position_output | Print surface optimisation results in a clean, minimal panel format. |
print_surface_position_table | |
build_surface_position_table ¶
build_surface_position_table(
title: str | None = "Surface Position",
)
Source code in pvgisprototype/cli/print/surface.py
def build_surface_position_table(
title: str | None = "Surface Position",
# index: bool,
# surface_position_data,
# timestamps,
# rounding_places,
# keys_to_sum: dict,
# keys_to_average: dict,
# keys_to_exclude: dict,
# time_column_name: RenderableType = "Time",
# time_column_footer: RenderableType = SYMBOL_SUMMATION,
# time_column_footer_style: str = "purple",
):
"""
"""
table = Table(
title=title,
title_style='dim',
# caption=caption.rstrip(', '), # Remove trailing comma + space
# caption_justify="left",
# expand=False,
# padding=(0, 1),
# box=SIMPLE_HEAD,
box=None,
show_edge=False,
pad_edge=False,
# header_style="bold gray50",
show_header=True,
header_style=None,
# show_footer=True,
# footer_style='white',
row_styles=["none", "dim"],
highlight=True,
)
table.add_column(
"Parameter",
justify="right",
style="dim",
no_wrap=True,
)
table.add_column(
"Angle",
justify="right",
)
table.add_column(
"Unit",
# justify="left",
)
table.add_column(
"Optimised",
style="bold",
justify="center",
)
table.add_column(
"Range",
justify="left",
style="dim",
)
return table
print_optimal_surface_position_output ¶
print_optimal_surface_position_output(
surface_position_data: dict,
surface_position: OptimalSurfacePosition,
surface_orientation: bool = True,
min_surface_orientation: float | None = None,
max_surface_orientation: float | None = None,
surface_tilt: bool = True,
min_surface_tilt: float | None = None,
max_surface_tilt: float | None = None,
photovoltaic_power: bool = True,
timestamps: DatetimeIndex | None = None,
timezone: ZoneInfo = ZoneInfo("UTC"),
title: str | None = None,
title_power: str | None = None,
subtitle_power: str | None = "Photovoltaic Power",
subtitle_position: str | None = "Surface Position",
subtitle_metadata="Metadata",
version: bool = False,
fingerprint: bool = False,
rounding_places: int = 3,
) -> None
Print surface optimisation results in a clean, minimal panel format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result | dict | Dictionary containing optimisation results with keys: - 'Surface Orientation': dict with 'value', 'optimal', 'unit' - 'Surface Tilt': dict with 'value', 'optimal', 'unit' - 'Mean PV Power': float - 'Unit': str (angle unit) - 'Timing': str (algorithm name) - 'Fingerprint 🆔': str (optional) | required |
rounding_places | int | Number of decimal places for rounding | 3 |
Source code in pvgisprototype/cli/print/surface.py
def print_optimal_surface_position_output(
surface_position_data: dict,
surface_position: OptimalSurfacePosition,
surface_orientation: bool = True,
min_surface_orientation: float | None = None,
max_surface_orientation: float | None = None,
surface_tilt: bool = True,
min_surface_tilt: float | None = None,
max_surface_tilt: float | None = None,
photovoltaic_power: bool = True,
timestamps: DatetimeIndex | None = None,
timezone: ZoneInfo = ZoneInfo('UTC'),
title: str | None = None, #"Optimal Position",
title_power: str | None = None,
subtitle_power: str | None = "Photovoltaic Power",
subtitle_position: str | None = "Surface Position",
subtitle_metadata = "Metadata",
version: bool = False,
fingerprint: bool = False,
rounding_places: int = 3,
) -> None:
"""
Print surface optimisation results in a clean, minimal panel format.
Parameters
----------
result : dict
Dictionary containing optimisation results with keys:
- 'Surface Orientation': dict with 'value', 'optimal', 'unit'
- 'Surface Tilt': dict with 'value', 'optimal', 'unit'
- 'Mean PV Power': float
- 'Unit': str (angle unit)
- 'Timing': str (algorithm name)
- 'Fingerprint 🆔': str (optional)
rounding_places : int, default=3
Number of decimal places for rounding
"""
# from devtools import debug
# debug(surface_position)
# Time might be Local
# if user_requested_timestamps is not None:
# time_column_name = LOCAL_TIME_COLUMN_NAME
# else:
# time_column_name = TIME_COLUMN_NAME
# if timestamps is not None:
# if user_requested_timezone is not None:
# if user_requested_timezone != ZoneInfo("UTC"):
# time_column_name = LOCAL_TIME_COLUMN_NAME
# timezone_string = f"Local Zone: [bold]{timezone}[/bold]"
# else:
# time_column_name = TIME_COLUMN_NAME
if timezone:
if timezone == ZoneInfo('UTC'):
timezone_string = f"[bold]{timezone}[/bold]"
else:
timezone_string = f"Local Zone: [bold]{timezone}[/bold]"
# Collect output data
if surface_orientation:
# orientation = surface_position_data.get('Surface Orientation', {})
orientation = surface_position.surface_orientation
orientation.value = round_float_values(orientation.value, rounding_places)
orientation.optimal = "[green]✓[/green]" if orientation.optimal else "[red]✗[/red]"
if min_surface_orientation and max_surface_orientation:
orientation_range = f"[{min_surface_orientation}, {max_surface_orientation}]"
if surface_tilt:
tilt = surface_position.surface_tilt
tilt.value = round_float_values(tilt.value, rounding_places)
tilt.optimal = "[green]✓" if tilt.optimal else "[red]✗"
tilt_range = f"[{min_surface_tilt}, {max_surface_tilt}]"
if photovoltaic_power:
minimum_power = float()
mean_power = surface_position.mean_photovoltaic_power
maximum_power = float()
if mean_power is not None:
mean_power = round_float_values(float(mean_power), rounding_places)
# Build output table
photovoltaic_power_table = Table(
title=title_power,
title_style='dim',
# caption=caption.rstrip(', '), # Remove trailing comma + space
# caption_justify="left",
# expand=False,
# padding=(0, 1),
# box=SIMPLE_HEAD,
box=None,
show_edge=False,
pad_edge=False,
# header_style="bold gray50",
show_header=True,
header_style=None,
# show_footer=True,
# footer_style='white',
row_styles=["none", "dim"],
highlight=True,
)
photovoltaic_power_table.add_column(
"Statistic",
justify="right",
style="dim",
no_wrap=True,
)
photovoltaic_power_table.add_column(
"Power",
justify="right",
)
photovoltaic_power_table.add_column(
"Unit",
# justify="left",
)
surface_position_table = build_surface_position_table(title=title)
# Populate output table
minimum_photovoltaic_power_row = []
photovoltaic_power_row = []
maximum_photovoltaic_power_row = []
orientation_row = []
tilt_row = []
if surface_orientation:
# surface_orientation_output = "Surface Orientation ⯐ "
orientation_row.append("Orientation ⯐")
orientation_row.append(f"{orientation.value}")
orientation_row.append(f"{orientation.unit}")
orientation_row.append(f"{orientation.optimal}")
if min_surface_orientation and max_surface_orientation:
orientation_row.append(orientation_range)
surface_position_table.add_row(*orientation_row)
if surface_tilt:
tilt_row.append("Tilt ⯐")
tilt_row.append(f"{tilt.value}")
tilt_row.append(f"{tilt.unit}")
tilt_row.append(f"{tilt.optimal}")
if tilt_range:
tilt_row.append(tilt_range)
surface_position_table.add_row(*tilt_row)
if photovoltaic_power:
minimum_photovoltaic_power_row.append("Min")
minimum_photovoltaic_power_row.append(f"[dim]{minimum_power}[/dim]")
minimum_photovoltaic_power_row.append('W')
photovoltaic_power_table.add_row(*minimum_photovoltaic_power_row)
photovoltaic_power_row.append("Mean")
photovoltaic_power_row.append(f"[bold yellow]{mean_power}[/bold yellow]")
photovoltaic_power_row.append('W')
photovoltaic_power_table.add_row(*photovoltaic_power_row)
maximum_photovoltaic_power_row.append("Max")
maximum_photovoltaic_power_row.append(f"[dim]{maximum_power}[/dim]")
maximum_photovoltaic_power_row.append('W')
photovoltaic_power_table.add_row(*maximum_photovoltaic_power_row)
# Metadata
metadata_table = Table(
box=None,
show_header=False,
show_edge=False,
pad_edge=False,
)
metadata_table.add_column(
justify="right",
style="none",
no_wrap=True,
)
metadata_table.add_column(justify="left")
# Build Panels
time_table = build_time_table()
frequency, frequency_label = infer_frequency_from_timestamps(timestamps)
time_table.add_row(
str(timestamps.strftime("%Y-%m-%d %H:%M").values[0]),
str(frequency) if frequency and frequency != "Single" else "-",
str(timestamps.strftime("%Y-%m-%d %H:%M").values[-1]),
str(timezone_string),
)
time_panel = build_time_panel(time_table, padding=(0, 1, 2, 1))
power_panel = Panel(
photovoltaic_power_table,
# title="Optimisation Results",
subtitle=subtitle_power,
subtitle_align='right',
# box=HORIZONTALS,
# box=SIMPLE_HEAD,
safe_box=True,
style="",
border_style='dim',
expand=False,
padding=(0, 1),
)
position_panel = Panel(
surface_position_table,
# title="Optimisation Results",
subtitle=subtitle_position,
subtitle_align='right',
# box=HORIZONTALS,
# box=SIMPLE_HEAD,
safe_box=True,
style="",
border_style='dim',
expand=False,
padding=(0, 1, 1, 1),
)
# Metadata Panel
# Timing algorithm
timing = surface_position_data.get("Timing")
if timing:
metadata_table.add_row("Timing :", f"[bold]{timing}[/bold]")
# Version & Fingerprint
fingerprint = retrieve_fingerprint(dictionary=surface_position_data)
version_and_fingerprint_and_column = build_version_and_fingerprint_columns(
version=version,
fingerprint=fingerprint,
)
metadata_panel = Panel(
metadata_table,
subtitle=f"[dim]{subtitle_metadata}[/dim]",
box=ROUNDED,
style="dim",
border_style="dim",
expand=False,
padding=(0, 1),
)
panels = []
panels.append(power_panel)
panels.append(position_panel)
panels.append(time_panel)
panels.append(metadata_panel)
# Print columns
columns = Columns(
panels,
# expand=False,
# equal=True,
# padding=2,
)
console.print(columns)
print_surface_position_table ¶
print_surface_position_table(
surface_position: dict,
longitude,
latitude,
timezone,
title="Surface Position",
version: bool = False,
fingerprint: bool = False,
rounding_places=ROUNDING_PLACES_DEFAULT,
)
Source code in pvgisprototype/cli/print/surface.py
def print_surface_position_table(
surface_position: dict,
longitude,
latitude,
timezone,
title="Surface Position",
version: bool = False,
fingerprint: bool = False,
rounding_places=ROUNDING_PLACES_DEFAULT,
):
"""
"""
panels = []
caption = build_simple_caption(
longitude,
latitude,
surface_position,
timezone,
user_requested_timezone=None,
)
# then : Create a Legend table for the symbols in question
legend = build_legend_table(
dictionary=surface_position,
caption=caption,
show_header=False,
box=None,
)
caption_panel = Panel(
caption,
subtitle="[gray]Reference[/gray]",
subtitle_align="right",
border_style="dim",
expand=False
)
legend_panel = Panel(
legend,
subtitle="[dim]Legend[/dim]",
subtitle_align="right",
border_style="dim",
expand=False,
padding=(0,1),
# style="dim",
)
# surface position Panel
table = Table(
box=None,
show_header=False,
show_edge=False,
pad_edge=False,
)
table.add_column(justify="right", style="none", no_wrap=True)
table.add_column(justify="left")
table.add_row(f"{LATITUDE_COLUMN_NAME} :", f"[bold]{latitude}[/bold]")
table.add_row(f"{LONGITUDE_COLUMN_NAME} :", f"[bold]{longitude}[/bold]")
# table.add_row("Time :", f"{timestamp[0]}")
table.add_row("Time zone :", f"{timezone}")
longest_label_length = max(len(key) for key in surface_position.keys())
surface_position_keys = {
SURFACE_ORIENTATION_NAME,
SURFACE_TILT_NAME,
ANGLE_UNIT_NAME,
INCIDENCE_DEFINITION,
UNIT_NAME,
}
for key, value in surface_position.items():
if key in surface_position_keys:
padded_key = f"{key} :".ljust(longest_label_length + 3, " ")
if key == INCIDENCE_DEFINITION:
value = f"[yellow]{value}[/yellow]"
table.add_row(padded_key, str(value))
position_panel = Panel(
table,
title="Surface Position",
box=HORIZONTALS,
style="",
expand=False,
padding=(0, 2),
)
panels.append(position_panel)
version_and_fingerprint_and_column = build_version_and_fingerprint_columns(
version=version,
fingerprint=fingerprint,
)
# Use Columns to place them side-by-side
from rich.columns import Columns
console.print(Columns([
caption_panel,
legend_panel,
]))
console.print(version_and_fingerprint_and_column)
symbols ¶
Functions:
| Name | Description |
|---|---|
create_symbols_table | |
print_pvgis_symbols | |
create_symbols_table ¶
Source code in pvgisprototype/cli/print/symbols.py
def create_symbols_table():
"""
"""
main_table = Table(
title='Symbol Descriptions',
caption='This table categorizes different symbols used throughout the system.',
show_header=False,
box=SIMPLE_HEAD,
highlight=True,
)
for category, symbols in SYMBOL_GROUPS_DESCRIPTIONS.items():
category_table = Table(box=SIMPLE_HEAD)
category_table.add_column("Symbol", justify="right", style="bright_blue", no_wrap=True)
category_table.add_column("Description", style="cyan")
for symbol, description in symbols.items():
category_table.add_row(f'[bold]{symbol}[/bold]', description)
main_table.add_row(category)
main_table.add_row(category_table)
main_table.add_row("")
return main_table
print_pvgis_symbols ¶
Source code in pvgisprototype/cli/print/symbols.py
time ¶
Functions:
| Name | Description |
|---|---|
build_time_panel | |
build_time_table | |
populate_time_table | |
build_time_panel ¶
build_time_panel(
time_table,
safe_box: bool = True,
expand: bool = False,
padding: tuple = (0, 2),
) -> Panel
Source code in pvgisprototype/cli/print/time.py
build_time_table ¶
Source code in pvgisprototype/cli/print/time.py
def build_time_table() -> Table:
""" """
time_table = Table(
box=None,
show_header=True,
header_style=None,
show_edge=False,
pad_edge=False,
)
time_table.add_column("Start", justify="left", style="bold")
time_table.add_column("Every", justify="left", style="dim bold")
time_table.add_column("End", justify="left", style="dim bold")
time_table.add_column("Zone", justify="left", style="dim bold")
return time_table
populate_time_table ¶
populate_time_table(
table: Table,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
) -> Table
Source code in pvgisprototype/cli/print/time.py
def populate_time_table(
table: Table,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
) -> Table:
"""
"""
frequency, frequency_label = infer_frequency_from_timestamps(timestamps)
table.add_row(
str(timestamps.strftime("%Y-%m-%d %H:%M").values[0]),
str(frequency) if frequency and frequency != 'Single' else '-',
str(timestamps.strftime("%Y-%m-%d %H:%M").values[-1]),
str(timezone),
)
return table
version ¶
Functions:
| Name | Description |
|---|---|
build_pvgis_version_panel | |
build_pvgis_version_panel ¶
build_pvgis_version_panel(
prefix_text: str = "PVGIS v6",
justify_text: JustifyMethod = "center",
style_text: str = "white dim",
border_style: str = "dim",
padding: tuple = (0, 2),
) -> Panel
Source code in pvgisprototype/cli/print/version.py
def build_pvgis_version_panel(
prefix_text: str = "PVGIS v6",
justify_text: JustifyMethod = "center",
style_text: str = "white dim",
border_style: str = "dim",
padding: tuple = (0, 2),
) -> Panel:
""" """
from pvgisprototype._version import __version__
pvgis_version = Text(
f"{prefix_text} ({__version__})",
justify=justify_text,
style=style_text,
)
return Panel(
pvgis_version,
# subtitle="[reverse]Fingerprint[/reverse]",
# subtitle_align="right",
border_style=border_style,
# style="dim",
expand=False,
padding=padding,
)
version_and_fingerprint ¶
Functions:
| Name | Description |
|---|---|
build_version_and_fingerprint_columns | Combine software version and fingerprint panels into a single Columns |
build_version_and_fingerprint_panels | Dynamically build panels based on available data. |
build_version_and_fingerprint_columns ¶
build_version_and_fingerprint_columns(
version: bool = False, fingerprint: bool = False
) -> Columns
Combine software version and fingerprint panels into a single Columns object.
Source code in pvgisprototype/cli/print/version_and_fingerprint.py
def build_version_and_fingerprint_columns(
version:bool = False,
fingerprint: bool = False,
) -> Columns:
"""Combine software version and fingerprint panels into a single Columns
object."""
version_and_fingeprint_panels = build_version_and_fingerprint_panels(
version=version,
fingerprint=fingerprint,
)
return Columns(version_and_fingeprint_panels, expand=False, padding=2)
build_version_and_fingerprint_panels ¶
build_version_and_fingerprint_panels(
version: bool = False, fingerprint: bool = False
) -> list[Panel]
Dynamically build panels based on available data.
Source code in pvgisprototype/cli/print/version_and_fingerprint.py
def build_version_and_fingerprint_panels(
version:bool = False,
fingerprint: bool = False,
) -> list[Panel]:
"""Dynamically build panels based on available data."""
# Always yield version panel
panels = []
if version:
panels.append(build_pvgis_version_panel())
# Yield fingerprint panel only if fingerprint is provided
if fingerprint:
panels.append(build_fingerprint_panel(fingerprint))
return panels
rich_help_panel_names ¶
Structural components of the command line interface
series ¶
Modules:
| Name | Description |
|---|---|
inspect | |
introduction | |
plot | |
resample | |
select | |
uniplot | |
inspect ¶
Functions:
| Name | Description |
|---|---|
inspect_xarray_supported_data | Select location series |
inspect_xarray_supported_data ¶
inspect_xarray_supported_data(
time_series: Annotated[
Path, typer_argument_time_series
],
encodings: bool = False,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
)
Select location series
Source code in pvgisprototype/cli/series/inspect.py
def inspect_xarray_supported_data(
time_series: Annotated[Path, typer_argument_time_series],
encodings: bool = False,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
):
"""Select location series"""
time_series_xarray = read_data_array_or_set(
input_data=time_series,
verbose=verbose,
)
print(time_series_xarray)
if encodings:
print(time_series_xarray.encoding)
introduction ¶
Functions:
| Name | Description |
|---|---|
series_introduction | A short introduction on the series command |
series_introduction ¶
A short introduction on the series command
Source code in pvgisprototype/cli/series/introduction.py
def series_introduction():
"""A short introduction on the series command"""
introduction = """
The [code]series[/code] command is a convenience wrapper around Xarray's
data processing capabilities.
Explain [bold cyan]timestamps[/bold cyan].
And more ...
"""
note = """
Timestamps are retrieved from the input data series. If the series are not
timestamped, then stamps are generated based on the user requested
combination of a three out of the four relevant parameters : `start-time`,
`end-time`, `frequency` and `period`.
"""
from rich.panel import Panel
note_in_a_panel = Panel(
"[italic]{}[/italic]".format(note),
title="[bold cyan]Note[/bold cyan]",
width=78,
)
from rich.console import Console
console = Console()
# introduction.wrap(console, 30)
console.print(introduction)
console.print(note_in_a_panel)
plot ¶
Functions:
| Name | Description |
|---|---|
plot | Plot selected time series |
plot ¶
plot(
time_series: Annotated[
Path, typer_argument_time_series
],
longitude: Annotated[
float, typer_argument_longitude_in_degrees
],
latitude: Annotated[
float, typer_argument_latitude_in_degrees
],
timestamps: Annotated[
DatetimeIndex, typer_argument_naive_timestamps
] = str(now()),
start_time: Annotated[
Timestamp | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
Timestamp | None, typer_option_end_time
] = None,
convert_longitude_360: Annotated[
bool, typer_option_convert_longitude_360
] = False,
variable: Annotated[
str | None, typer_option_data_variable
] = None,
default_dimension: Annotated[
str, "Default dimension"
] = "time",
ask_for_dimension: Annotated[
bool, "Ask to plot a specific dimension"
] = True,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
output_filename: Annotated[
Path, typer_option_output_filename
] = None,
variable_name_as_suffix: Annotated[
bool, typer_option_variable_name_as_suffix
] = True,
width: Annotated[int, "Width for the plot"] = 16,
height: Annotated[int, "Height for the plot"] = 3,
tufte_style: Annotated[
bool, typer_option_tufte_style
] = False,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
data_source: Annotated[
str,
Option(
help="Data source text to print in the footer of the plot."
),
] = "",
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = VERBOSE_LEVEL_DEFAULT,
)
Plot selected time series
Source code in pvgisprototype/cli/series/plot.py
def plot(
time_series: Annotated[Path, typer_argument_time_series],
longitude: Annotated[float, typer_argument_longitude_in_degrees],
latitude: Annotated[float, typer_argument_latitude_in_degrees],
timestamps: Annotated[DatetimeIndex, typer_argument_naive_timestamps] = str(Timestamp.now()),
start_time: Annotated[
Timestamp | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
Timestamp | None, typer_option_end_time
] = None, # Used by a callback function
convert_longitude_360: Annotated[bool, typer_option_convert_longitude_360] = False,
variable: Annotated[str | None, typer_option_data_variable] = None,
default_dimension: Annotated[str, 'Default dimension'] = 'time',
ask_for_dimension: Annotated[bool, "Ask to plot a specific dimension"] = True,
# slice_options: Annotated[bool, "Slice data dimensions"] = False,
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
output_filename: Annotated[Path, typer_option_output_filename] = None,
variable_name_as_suffix: Annotated[
bool, typer_option_variable_name_as_suffix
] = True,
width: Annotated[int, "Width for the plot"] = 16,
height: Annotated[int, "Height for the plot"] = 3,
tufte_style: Annotated[bool, typer_option_tufte_style] = False,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
data_source: Annotated[str, typer.Option(help="Data source text to print in the footer of the plot.")] = '',
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = VERBOSE_LEVEL_DEFAULT,
):
"""Plot selected time series"""
data_array = select_time_series(
time_series=time_series,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
start_time=start_time,
end_time=end_time,
variable=variable,
# convert_longitude_360=convert_longitude_360,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
# in_memory=in_memory,
verbose=verbose,
log=log,
)
try:
plot_series(
data_array=data_array,
time=timestamps,
default_dimension=default_dimension,
ask_for_dimension=ask_for_dimension,
# slice_options=slice_options,
figure_name=output_filename.name,
save_path=output_filename.parent,
# add_offset=add_offset,
variable_name_as_suffix=variable_name_as_suffix,
tufte_style=tufte_style,
width=width,
height=height,
resample_large_series=resample_large_series,
data_source=data_source,
fingerprint=fingerprint,
)
except Exception as exception:
logger.error(
f"{ERROR_IN_PLOTTING_DATA} : {exception}",
alt=f"{ERROR_IN_PLOTTING_DATA} : {exception}"
)
raise SystemExit(33)
resample ¶
Functions:
| Name | Description |
|---|---|
resample | Time-based groupby of solar radiation and PV output power time series over a location. |
resample ¶
resample(indexer: str | None = None)
Time-based groupby of solar radiation and PV output power time series over a location.
For example: - solar radiation on horizontal and inclined planes - Direct Normal Irradiation (DNI) and more in various - the daily variation in the clear-sky radiation
- hourly
- daily
- monthly
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
indexer | str | None | | None |
Source code in pvgisprototype/cli/series/resample.py
def resample(
indexer: str | None = None, # The offset string or object representing target conversion.
# or : Mapping from a date-time dimension to resample frequency [1]
):
"""Time-based groupby of solar radiation and PV output power time series over a location.
For example:
- solar radiation on horizontal and inclined planes
- Direct Normal Irradiation (DNI) and more in various
- the daily variation in the clear-sky radiation
- hourly
- daily
- monthly
Parameters
----------
indexer: str
"""
pass
select ¶
Functions:
| Name | Description |
|---|---|
select | Select location series |
select_fast | Bare read & write |
select_sarah | Select location series |
warn_for_negative_longitude | Warn for negative longitude value |
write_to_netcdf | Save to NetCDF with lon and lat dimensions of length 1. |
select ¶
select(
time_series: Annotated[
Path, typer_argument_time_series
],
longitude: Annotated[
float, typer_argument_longitude_in_degrees
],
latitude: Annotated[
float, typer_argument_latitude_in_degrees
],
time_series_2: Annotated[
Path, typer_option_time_series
] = None,
timestamps: Annotated[
DatetimeIndex | None,
typer_argument_naive_timestamps,
] = str(now()),
start_time: Annotated[
Timestamp | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
Timestamp | None, typer_option_end_time
] = None,
convert_longitude_360: Annotated[
bool, typer_option_convert_longitude_360
] = False,
variable: Annotated[
str | None, typer_option_data_variable
] = None,
variable_2: Annotated[
str | None, typer_option_data_variable
] = None,
coordinate: Annotated[
str, typer_option_wavelength_column_name
] = None,
filter_coordinate: Annotated[
bool,
Option(
help="Limit the range of input data by filtering the requested Xarray `coordinate`. See options `minimum`, `maximum`.",
rich_help_panel=rich_help_panel_spectrum,
),
] = False,
minimum: Annotated[
float | None,
typer_option_minimum_spectral_irradiance_wavelength,
] = None,
maximum: Annotated[
float | None,
typer_option_maximum_spectral_irradiance_wavelength,
] = None,
neighbor_lookup: Annotated[
MethodForInexactMatches | None,
typer_option_nearest_neighbor_lookup,
] = None,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
output_filename: Annotated[
Path, typer_option_output_filename
] = None,
variable_name_as_suffix: Annotated[
bool, typer_option_variable_name_as_suffix
] = True,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
uniplot: Annotated[
bool, typer_option_uniplot
] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
lines: Annotated[
bool, typer_option_uniplot_lines
] = True,
title: Annotated[
str | None, typer_option_uniplot_title
] = "Selected data",
unit: Annotated[
str, typer_option_uniplot_unit
] = UNIT_NAME,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
log: Annotated[
int, typer_option_log
] = VERBOSE_LEVEL_DEFAULT,
data_source: Annotated[
str,
Option(
help="Data source text to print in the footer of the plot."
),
] = "",
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
)
Select location series
Source code in pvgisprototype/cli/series/select.py
def select(
time_series: Annotated[Path, typer_argument_time_series],
longitude: Annotated[float, typer_argument_longitude_in_degrees],
latitude: Annotated[float, typer_argument_latitude_in_degrees],
time_series_2: Annotated[Path, typer_option_time_series] = None,
timestamps: Annotated[DatetimeIndex | None, typer_argument_naive_timestamps] = str(Timestamp.now()),
start_time: Annotated[
Timestamp | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
Timestamp | None, typer_option_end_time
] = None, # Used by a callback function
convert_longitude_360: Annotated[bool, typer_option_convert_longitude_360] = False,
variable: Annotated[str | None, typer_option_data_variable] = None,
variable_2: Annotated[str | None, typer_option_data_variable] = None,
coordinate: Annotated[
str,
typer_option_wavelength_column_name, # Update Me
] = None,
filter_coordinate: Annotated[
bool,
typer.Option(
help="Limit the range of input data by filtering the requested Xarray `coordinate`. See options `minimum`, `maximum`.",
rich_help_panel=rich_help_panel_spectrum,
),
] = False,
minimum: Annotated[
float | None, typer_option_minimum_spectral_irradiance_wavelength # Update Me
] = None,
maximum: Annotated[
float | None, typer_option_maximum_spectral_irradiance_wavelength # Update Me
] = None,
neighbor_lookup: Annotated[
MethodForInexactMatches | None, typer_option_nearest_neighbor_lookup
] = None, # NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
output_filename: Annotated[
Path, typer_option_output_filename
] = None,
variable_name_as_suffix: Annotated[
bool, typer_option_variable_name_as_suffix
] = True,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
uniplot: Annotated[bool, typer_option_uniplot] = UNIPLOT_FLAG_DEFAULT,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
lines: Annotated[bool, typer_option_uniplot_lines] = True,
title: Annotated[str | None, typer_option_uniplot_title] = 'Selected data',
unit: Annotated[str, typer_option_uniplot_unit] = UNIT_NAME, # " °C")
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
log: Annotated[int, typer_option_log] = VERBOSE_LEVEL_DEFAULT,
data_source: Annotated[str, typer.Option(help="Data source text to print in the footer of the plot.")] = '',
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
# quick_response_code: Annotated[
# QuickResponseCode, typer_option_quick_response
# ] = QuickResponseCode.NoneValue,
):
"""Select location series"""
if convert_longitude_360:
longitude = longitude % 360
warn_for_negative_longitude(longitude)
if not variable:
dataset = open_dataset(time_series)
# ----------------------------------------------------- Review Me ----
#
if len(dataset.data_vars) >= 2:
variables = list(dataset.data_vars.keys())
print(f"The dataset contains more than one variable : {variables}")
variable = typer.prompt(
"Please specify the variable you are interested in from the above list"
)
else:
variable = list(dataset.data_vars)
#
# ----------------------------------------------------- Review Me ----
location_time_series = select_time_series(
time_series=time_series,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
start_time=start_time,
end_time=end_time,
# convert_longitude_360=convert_longitude_360,
variable=variable,
coordinate=coordinate,
minimum=minimum,
maximum=maximum,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
# variable_name_as_suffix=variable_name_as_suffix,
verbose=verbose,
log=log,
)
if resample_large_series:
location_time_series = location_time_series.resample(time="1M").mean()
location_time_series_2 = select_time_series(
time_series=time_series_2,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
start_time=start_time,
end_time=end_time,
# convert_longitude_360=convert_longitude_360,
variable=variable_2,
coordinate=coordinate,
minimum=minimum,
maximum=maximum,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
# variable_name_as_suffix=variable_name_as_suffix,
verbose=verbose,
log=log,
)
if resample_large_series:
location_time_series_2 = location_time_series_2.resample(time="1M").mean()
results = {
location_time_series.name: location_time_series.to_numpy(),
}
if location_time_series_2 is not None:
more_results = {
location_time_series_2.name: (
location_time_series_2.to_numpy()
if location_time_series_2 is not None
else None
)
}
results = results | more_results
# if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
# debug(locals())
if not quiet:
if verbose > 0:
# special case!
if location_time_series is not None and timestamps is None:
timestamps = location_time_series.time.to_numpy()
if isinstance(location_time_series, DataArray):
from pvgisprototype.cli.print.irradiance.table import print_irradiance_xarray
print_irradiance_xarray(
location_time_series=location_time_series,
longitude=longitude,
latitude=latitude,
# elevation=elevation,
title=title,
rounding_places=rounding_places,
verbose=verbose,
# index=index,
)
if isinstance(location_time_series_2, DataArray):
print_irradiance_xarray(
location_time_series=location_time_series_2,
longitude=longitude,
latitude=latitude,
# elevation=elevation,
title=title,
rounding_places=rounding_places,
verbose=verbose,
# index=index,
)
else:
from pvgisprototype.cli.print.irradiance.data import print_irradiance_table_2
print_irradiance_table_2(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dictionary=results,
title=title,
rounding_places=rounding_places,
verbose=verbose,
)
# if location_time_series_2 is not None:
# print_irradiance_table_2(
# longitude=longitude,
# latitude=latitude,
# timestamps=timestamps,
# dictionary=results,
# title=title,
# rounding_places=rounding_places,
# verbose=verbose,
# )
else:
flat_list = location_time_series.to_numpy().flatten().astype(str)
csv_str = ",".join(flat_list)
print(csv_str)
# statistics after echoing series which might be Long!
if statistics:
from pvgisprototype.api.series.statistics import print_series_statistics
print_series_statistics(
data_array=location_time_series,
timestamps=timestamps,
groupby=groupby,
title="Selected series",
rounding_places=rounding_places,
)
if fingerprint:
from pvgisprototype.cli.print.fingerprint import print_finger_hash
print_finger_hash(dictionary=results)
if uniplot:
import os
terminal_columns, _ = os.get_terminal_size() # we don't need lines!
terminal_length = int(terminal_columns * terminal_width_fraction)
from functools import partial
from uniplot import plot as default_plot
plot = partial(default_plot, width=terminal_length)
if isinstance(location_time_series, DataArray) and location_time_series.size == 1:
print(
f"{exclamation_mark} I [red]cannot[/red] plot a single scalar value {location_time_series.item()}!"
)
raise typer.Abort()
if not isinstance(location_time_series, DataArray):
print("Selected variable did not return a DataArray. Check your selection.")
return
if isinstance(location_time_series, DataArray):
supertitle = getattr(location_time_series, "long_name", "Untitled")
title += f' at ({longitude}, {latitude})'
label = getattr(location_time_series, "name", None)
label_2 = (
getattr(location_time_series_2, "name", None)
if isinstance(location_time_series_2, DataArray)
else None
)
data_source_text = ''
if data_source:
data_source_text = f" · {data_source}"
if fingerprint:
from pvgisprototype.core.hashing import generate_hash
data_source_text += f" · Fingerprint : {generate_hash(location_time_series)}"
if label_2:
label_2 += data_source_text
else:
label += data_source_text
unit = getattr(location_time_series, "units", None)
if coordinate in location_time_series.coords:
y_series = (
[location_time_series, location_time_series_2]
if location_time_series_2 is not None
else location_time_series
)
if location_time_series.time.size == 1:
x_series = location_time_series[coordinate].values
title += f' on {str(timestamps[0])}'
x_unit = ' ' + getattr(location_time_series[coordinate], 'units', '')
else:
x_unit = ''
if location_time_series[coordinate].size == 1:
# Case 2: One level of coordinate, time on x-axis
# x_series = location_time_series.time.values
# x_series = [[str(timestamp)] for timestamp in location_time_series['time'].values]
x_series = location_time_series.time.values
y_series = y_series.values.flatten()
title += f' at {location_time_series[coordinate].item()}'
else:
# Case 3: Multiple levels of the coordinate and multiple timestamps
# # x_series = [location_time_series[coordinate].values] * len(y_series)
# x_series = [location_time_series[coordinate].values]
for level in location_time_series[coordinate].values:
level_data = location_time_series.sel({coordinate: level})
x_series = level_data.time.values
y_series = level_data.values.flatten() # Flatten to 1D
plot_title = f'{title} at {level} {getattr(location_time_series[coordinate], "units", "")}'
plot_x_unit = 'time'
plot(
xs=x_series,
ys=y_series,
legend_labels=[label],
lines=lines,
title=plot_title,
x_unit=plot_x_unit,
y_unit=' ' + str(unit),
)
return
plot(
# x=location_time_series,
xs=x_series,
ys=y_series,
legend_labels=[label, label_2],
lines=lines,
title=title if title else supertitle,
x_unit=x_unit,
y_unit=' ' + str(unit),
# force_ascii=True,
)
else:
# plot over time
plot(
# x=location_time_series,
# xs=location_time_series,
ys=[location_time_series, location_time_series_2] if location_time_series_2 is not None else location_time_series,
legend_labels=[label, label_2],
lines=lines,
title=title if title else supertitle,
y_unit=" " + str(unit),
# force_ascii=True,
)
output_handlers = {
".nc": lambda location_time_series, path: write_to_netcdf(
location_time_series=location_time_series,
path=path,
longitude=longitude,
latitude=latitude
),
".csv": lambda location_time_series, path: to_csv(
x=location_time_series, path=path
),
}
if output_filename:
extension = output_filename.suffix.lower()
if extension in output_handlers:
output_handlers[extension](location_time_series, output_filename)
else:
raise ValueError(f"Unsupported file extension: {extension}")
select_fast ¶
select_fast(
time_series: Annotated[
Path, typer_argument_time_series
],
longitude: Annotated[
float, typer_argument_longitude_in_degrees
],
latitude: Annotated[
float, typer_argument_latitude_in_degrees
],
time_series_2: Annotated[
Path, typer_option_time_series
] = None,
tolerance: Annotated[
float | None, typer_option_tolerance
] = 0.1,
output_filename: Annotated[
Path | None, typer_option_output_filename
] = None,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
)
Bare read & write
Source code in pvgisprototype/cli/series/select.py
def select_fast(
time_series: Annotated[Path, typer_argument_time_series],
longitude: Annotated[float, typer_argument_longitude_in_degrees],
latitude: Annotated[float, typer_argument_latitude_in_degrees],
time_series_2: Annotated[Path, typer_option_time_series] = None,
tolerance: Annotated[
float | None, typer_option_tolerance
] = 0.1, # Customize default if needed
# in_memory: Annotated[bool, typer_option_in_memory] = False,
output_filename: Annotated[Path | None, typer_option_output_filename] = None,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
):
"""Bare read & write"""
try:
series = open_dataarray(time_series).sel(
lon=longitude, lat=latitude, method="nearest"
)
if time_series_2:
series_2 = open_dataarray(time_series_2).sel(
lon=longitude, lat=latitude, method="nearest"
)
# Is .nc needed in the context of this command ? ---------------------
output_handlers = {
# ".nc": lambda location_time_series, path: location_time_series.to_netcdf(path),
".csv": lambda location_time_series, path: to_csv(
x=location_time_series, path=path
),
}
if output_filename:
extension = output_filename.suffix.lower()
if extension in output_handlers:
output_handlers[extension](location_time_series, output_filename)
else:
raise ValueError(f"Unsupported file extension: {extension}")
print("Done.-")
except Exception as e:
print(f"An error occurred: {e}")
select_sarah ¶
select_sarah(
time_series: Annotated[
Path, typer_argument_time_series
],
longitude: Annotated[
float, typer_argument_longitude_in_degrees
],
latitude: Annotated[
float, typer_argument_latitude_in_degrees
],
time_series_2: Annotated[
Path, typer_option_time_series
] = None,
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now()),
start_time: Annotated[
Timestamp | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
Timestamp | None, typer_option_end_time
] = None,
convert_longitude_360: Annotated[
bool, typer_option_convert_longitude_360
] = False,
variable: Annotated[
str | None, typer_option_data_variable
] = None,
variable_2: Annotated[
str | None, typer_option_data_variable
] = None,
wavelength_column: Annotated[
str, typer_option_wavelength_column_name
] = WAVELENGTHS_CSV_COLUMN_NAME_DEFAULT,
limit_spectral_range: Annotated[
bool,
Option(
help="Limit the spectral range of the irradiance input data. Default for `spectral_mismatch_model = Pelland`"
),
] = True,
min_wavelength: Annotated[
float,
typer_option_minimum_spectral_irradiance_wavelength,
] = MIN_WAVELENGTH,
max_wavelength: Annotated[
float,
typer_option_maximum_spectral_irradiance_wavelength,
] = MAX_WAVELENGTH,
neighbor_lookup: Annotated[
MethodForInexactMatches,
typer_option_nearest_neighbor_lookup,
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[
float | None, typer_option_tolerance
] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[
bool, typer_option_in_memory
] = IN_MEMORY_FLAG_DEFAULT,
statistics: Annotated[
bool, typer_option_statistics
] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[
str | None, typer_option_groupby
] = GROUPBY_DEFAULT,
output_filename: Annotated[
Path | None, typer_option_output_filename
] = None,
variable_name_as_suffix: Annotated[
bool, typer_option_variable_name_as_suffix
] = True,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = VERBOSE_LEVEL_DEFAULT,
)
Select location series
Source code in pvgisprototype/cli/series/select.py
def select_sarah(
time_series: Annotated[Path, typer_argument_time_series],
longitude: Annotated[float, typer_argument_longitude_in_degrees],
latitude: Annotated[float, typer_argument_latitude_in_degrees],
time_series_2: Annotated[Path, typer_option_time_series] = None,
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(Timestamp.now()),
start_time: Annotated[
Timestamp | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
Timestamp | None, typer_option_end_time
] = None, # Used by a callback function
convert_longitude_360: Annotated[bool, typer_option_convert_longitude_360] = False,
variable: Annotated[str | None, typer_option_data_variable] = None,
variable_2: Annotated[str | None, typer_option_data_variable] = None,
wavelength_column: Annotated[
str,
typer_option_wavelength_column_name,
] = WAVELENGTHS_CSV_COLUMN_NAME_DEFAULT,
limit_spectral_range: Annotated[
bool,
typer.Option(help="Limit the spectral range of the irradiance input data. Default for `spectral_mismatch_model = Pelland`")
] = True,
min_wavelength: Annotated[
float, typer_option_minimum_spectral_irradiance_wavelength
] = MIN_WAVELENGTH,
max_wavelength: Annotated[
float, typer_option_maximum_spectral_irradiance_wavelength
] = MAX_WAVELENGTH,
neighbor_lookup: Annotated[
MethodForInexactMatches, typer_option_nearest_neighbor_lookup
] = NEIGHBOR_LOOKUP_DEFAULT,
tolerance: Annotated[float | None, typer_option_tolerance] = TOLERANCE_DEFAULT,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = MASK_AND_SCALE_FLAG_DEFAULT,
in_memory: Annotated[bool, typer_option_in_memory] = IN_MEMORY_FLAG_DEFAULT,
statistics: Annotated[bool, typer_option_statistics] = STATISTICS_FLAG_DEFAULT,
groupby: Annotated[str | None, typer_option_groupby] = GROUPBY_DEFAULT,
output_filename: Annotated[Path | None, typer_option_output_filename] = None,
variable_name_as_suffix: Annotated[
bool, typer_option_variable_name_as_suffix
] = True,
rounding_places: Annotated[
int | None, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = VERBOSE_LEVEL_DEFAULT,
):
"""Select location series"""
if convert_longitude_360:
longitude = longitude % 360
warn_for_negative_longitude(longitude)
if not variable:
dataset = open_dataset(time_series)
# ----------------------------------------------------- Review Me ----
#
if len(dataset.data_vars) >= 2:
variables = list(dataset.data_vars.keys())
print(f"The dataset contains more than one variable : {variables}")
variable = typer.prompt(
"Please specify the variable you are interested in from the above list"
)
else:
variable = list(dataset.data_vars)
#
# ----------------------------------------------------- Review Me ----
location_time_series = select_time_series(
time_series=time_series,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
start_time=start_time,
end_time=end_time,
# convert_longitude_360=convert_longitude_360,
variable=variable,
coordinate=wavelength_column,
minimum=min_wavelength,
maximum=max_wavelength,
drop=limit_spectral_range,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
# variable_name_as_suffix=variable_name_as_suffix,
verbose=verbose,
log=log,
)
location_time_series_2 = select_time_series(
time_series=time_series_2,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
start_time=start_time,
end_time=end_time,
# convert_longitude_360=convert_longitude_360,
variable=variable_2,
coordinate=wavelength_column,
minimum=min_wavelength,
maximum=max_wavelength,
drop=limit_spectral_range,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
in_memory=in_memory,
# variable_name_as_suffix=variable_name_as_suffix,
verbose=verbose,
log=log,
)
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
results = {
location_time_series.name: location_time_series.to_numpy(),
}
if location_time_series_2 is not None:
more_results = {
location_time_series_2.name: (
location_time_series_2.to_numpy()
if location_time_series_2 is not None
else None
)
}
results = results | more_results
title = "Location time series"
if verbose:
# special case!
if location_time_series is not None and timestamps is None:
from pvgisprototype.cli.print.irradiance import print_irradiance_table_2
timestamps = location_time_series.time.to_numpy()
# if isinstance(location_time_series, DataArray):
# print_irradiance_xarray(
# location_time_series=location_time_series,
# longitude=longitude,
# latitude=latitude,
# # elevation=elevation,
# title=title,
# rounding_places=rounding_places,
# verbose=verbose,
# # index=index,
# )
# else:
print_irradiance_table_2(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dictionary=results,
title=title,
rounding_places=rounding_places,
verbose=verbose,
)
# statistics after echoing series which might be Long!
if statistics:
from pvgisprototype.api.series.statistics import print_series_statistics
print_series_statistics(
data_array=location_time_series,
timestamps=timestamps,
groupby=groupby,
title="Selected series",
rounding_places=rounding_places,
)
# if csv:
# from pvgisprototype.cli.write import export_statistics_to_csv
# export_statistics_to_csv(
# data_array=location_time_series,
# filename=csv,
# )
output_handlers = {
".nc": lambda location_time_series, path: location_time_series.to_netcdf(path),
".csv": lambda location_time_series, path: to_csv(
x=location_time_series, path=path
),
}
if output_filename:
extension = output_filename.suffix.lower()
if extension in output_handlers:
output_handlers[extension](location_time_series, output_filename)
else:
raise ValueError(f"Unsupported file extension: {extension}")
warn_for_negative_longitude ¶
Warn for negative longitude value
Maybe the input dataset ranges in [0, 360] degrees ?
Source code in pvgisprototype/cli/series/select.py
def warn_for_negative_longitude(
longitude: Longitude = None,
):
"""Warn for negative longitude value
Maybe the input dataset ranges in [0, 360] degrees ?
"""
if longitude < 0:
warning = f"{exclamation_mark} "
warning += "The longitude "
warning += f"{longitude} " + "is negative. "
warning += "If the input dataset's longitude values range in [0, 360], consider using `--convert-longitude-360`!"
logger.warning(warning)
write_to_netcdf ¶
Save to NetCDF with lon and lat dimensions of length 1.
Source code in pvgisprototype/cli/series/select.py
def write_to_netcdf(
location_time_series,
path,
longitude,
latitude,
):
"""Save to NetCDF with lon and lat dimensions of length 1."""
# A new 'coords' for longitude and latitude, each of length 1
new_coords = {
'lon': numpy.array([longitude]),
'lat': numpy.array([latitude]),
'time': location_time_series.time
}
# Expand the data to include lat and lon dimensions
new_data = location_time_series.values[:, numpy.newaxis, numpy.newaxis]
# Create the new DataArray
data_array = xarray.DataArray(
new_data,
coords=new_coords,
dims=['time', 'lat', 'lon'],
name=location_time_series.name,
attrs=location_time_series.attrs,
)
# Add attributes to longitude and latitude for CF compliance
data_array.lon.attrs['long_name'] = 'longitude'
data_array.lon.attrs['units'] = 'degrees_east'
data_array.lat.attrs['long_name'] = 'latitude'
data_array.lat.attrs['units'] = 'degrees_north'
# Save to NetCDF
data_array.to_netcdf(path)
uniplot ¶
Functions:
| Name | Description |
|---|---|
uniplot | Plot time series in the terminal |
uniplot ¶
uniplot(
time_series: Annotated[
Path, typer_argument_time_series
],
longitude: Annotated[
float, typer_argument_longitude_in_degrees
],
latitude: Annotated[
float, typer_argument_latitude_in_degrees
],
time_series_2: Annotated[
Path | None, typer_option_time_series
] = None,
timestamps: Annotated[
DatetimeIndex | None, typer_argument_timestamps
] = None,
start_time: Annotated[
Timestamp | None, typer_option_start_time
] = None,
end_time: Annotated[
Timestamp | None, typer_option_end_time
] = None,
variable: Annotated[
str | None, typer_option_data_variable
] = None,
coordinate: str | None = None,
neighbor_lookup: Annotated[
MethodForInexactMatches | None,
typer_option_nearest_neighbor_lookup,
] = None,
tolerance: Annotated[
float | None, typer_option_tolerance
] = 0.1,
mask_and_scale: Annotated[
bool, typer_option_mask_and_scale
] = False,
resample_large_series: Annotated[
bool, "Resample large time series?"
] = False,
lines: Annotated[
bool, typer_option_uniplot_lines
] = True,
title: Annotated[
str | None, typer_option_uniplot_title
] = None,
unit: Annotated[
str, typer_option_uniplot_unit
] = UNIT_NAME,
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
data_source: Annotated[
str,
Option(
help="Data source text to print in the footer of the plot."
),
] = "",
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
)
Plot time series in the terminal
Source code in pvgisprototype/cli/series/uniplot.py
def uniplot(
time_series: Annotated[Path, typer_argument_time_series],
longitude: Annotated[float, typer_argument_longitude_in_degrees],
latitude: Annotated[float, typer_argument_latitude_in_degrees],
time_series_2: Annotated[Path | None, typer_option_time_series] = None,
timestamps: Annotated[DatetimeIndex | None, typer_argument_timestamps] = None,
start_time: Annotated[Timestamp | None, typer_option_start_time] = None,
end_time: Annotated[Timestamp | None, typer_option_end_time] = None,
variable: Annotated[str | None, typer_option_data_variable] = None,
coordinate: str | None = None,
# convert_longitude_360: Annotated[bool, typer_option_convert_longitude_360] = False,
neighbor_lookup: Annotated[
MethodForInexactMatches | None, typer_option_nearest_neighbor_lookup
] = None,
tolerance: Annotated[
float | None, typer_option_tolerance
] = 0.1, # Customize default if needed
mask_and_scale: Annotated[bool, typer_option_mask_and_scale] = False,
resample_large_series: Annotated[bool, "Resample large time series?"] = False,
lines: Annotated[bool, typer_option_uniplot_lines] = True,
title: Annotated[str | None, typer_option_uniplot_title] = None,
unit: Annotated[str, typer_option_uniplot_unit] = UNIT_NAME, # " °C")
terminal_width_fraction: Annotated[
float, typer_option_uniplot_terminal_width
] = TERMINAL_WIDTH_FRACTION,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
data_source: Annotated[str, typer.Option(help="Data source text to print in the footer of the plot.")] = '',
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
):
"""Plot time series in the terminal"""
import os
terminal_columns, _ = os.get_terminal_size() # we don't need lines!
terminal_length = int(terminal_columns * terminal_width_fraction)
from functools import partial
from uniplot import plot as default_plot
plot = partial(default_plot, width=terminal_length)
data_array = select_time_series(
time_series=time_series,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
start_time=start_time,
end_time=end_time,
variable=variable,
# convert_longitude_360=convert_longitude_360,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
# in_memory=in_memory,
verbose=verbose,
)
if resample_large_series:
data_array = data_array.resample(time="1M").mean()
data_array_2 = select_time_series(
time_series=time_series_2,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
start_time=start_time,
end_time=end_time,
variable=variable,
# convert_longitude_360=convert_longitude_360,
neighbor_lookup=neighbor_lookup,
tolerance=tolerance,
mask_and_scale=mask_and_scale,
# in_memory=in_memory,
verbose=verbose,
)
if resample_large_series:
data_array_2 = data_array_2.resample(time="1M").mean()
if isinstance(data_array, float):
print(
f"⚠️{exclamation_mark} [red]Aborting[/red] as I [red]cannot[/red] plot the single float value {float}!"
)
typer.Abort()
if not isinstance(data_array, DataArray):
print("Selected variable did not return a DataArray. Check your selection.")
return
if isinstance(data_array, DataArray):
supertitle = getattr(data_array, "long_name", "Untitled")
label = getattr(data_array, "name", None)
label_2 = (
getattr(data_array_2, "name", None)
if isinstance(data_array_2, DataArray)
else None
)
data_source_text = ''
if data_source:
data_source_text = f" · {data_source}"
if fingerprint:
from pvgisprototype.core.hashing import generate_hash
data_source_text += f" · Fingerprint : {generate_hash(data_array)}"
if label_2:
label_2 += data_source_text
else:
label += data_source_text
unit = getattr(data_array, "units", None)
if coordinate in data_array.coords:
plot(
# x=data_array,
xs=data_array[coordinate],
ys=[data_array, data_array_2] if data_array_2 is not None else data_array,
legend_labels=[label, label_2],
lines=lines,
title=title if title else supertitle,
x_unit=' ' + getattr(data_array[coordinate], 'units', ''),
y_unit=' ' + str(unit),
# force_ascii=True,
)
else:
# plot over time
plot(
# x=data_array,
# xs=data_array,
ys=[data_array, data_array_2] if data_array_2 is not None else data_array,
legend_labels=[label, label_2],
lines=lines,
title=title if title else supertitle,
y_unit=" " + str(unit),
# force_ascii=True,
)
surface ¶
Modules:
| Name | Description |
|---|---|
elevation | |
horizon | |
elevation ¶
Functions:
| Name | Description |
|---|---|
get_elevation | Retrieve the location elevation from digital elevation data |
get_elevation ¶
get_elevation(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
)
Retrieve the location elevation from digital elevation data
Args: longitude latitude
Notes: - Based on the original C program readelevation: - Variable, Type, Range, Default, Notes - lat, float, [-90, 90], -, Required - lon, float, [-180, 180], -, Required
Source code in pvgisprototype/cli/surface/elevation.py
def get_elevation(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
):
"""
Retrieve the location elevation from digital elevation data
Args:
longitude
latitude
Notes:
- Based on the original C program `readelevation`:
- Variable, Type, Range, Default, Notes
- lat, float, [-90, 90], -, Required
- lon, float, [-180, 180], -, Required
"""
pass
horizon ¶
Functions:
| Name | Description |
|---|---|
get_horizon | Calculate the entire horizon angle height (in radians) around a single point from a digital elevation model |
get_horizon ¶
get_horizon(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
)
Calculate the entire horizon angle height (in radians) around a single point from a digital elevation model
Notes: - Based on the original C program horizon_out - Variable, Typr, Range, Default, Notes - lat, float, [-90, 90], -, Latitude in decimal degrees, south is negative. Required - lon, float, [-180, 180], - , Longitude in decimal degrees, west is negative. Required - userhorizon, list, List of float values ranging in [0, 90] separated by comma (CSV) (length < =365), -, Height of the horizon at equidistant directions around the point of interest, in degrees. Starting at north and moving clockwise. The series 0, 10, 20, 30, 40, 15, 25, 5 would mean the horizon height is 0° due north, 10° for north-east, 20° for east, 30° for south-east, and so on. Optional, Depends on userhorizon=1, - outputformat, str, [csv, basic, json], csv, Output format. csv: CSV with text explanations, basic: CSV. Optional - browser, bool, 0, 1, 0, Setting browser=1 and accessing the service through a web browser, will save the retrieved data to a file. Optional
Source code in pvgisprototype/cli/surface/horizon.py
def get_horizon(
longitude: Annotated[float, typer_argument_longitude],
latitude: Annotated[float, typer_argument_latitude],
):
"""Calculate the entire horizon angle height (in radians) around a single point from a digital elevation model
Notes:
- Based on the original C program `horizon_out`
- Variable, Typr, Range, Default, Notes
- lat, float, [-90, 90], -, Latitude in decimal degrees, south is negative. Required
- lon, float, [-180, 180], - , Longitude in decimal degrees, west is negative. Required
- userhorizon, list, List of float values ranging in [0, 90] separated by comma (CSV) (length < =365), -, Height of the horizon at equidistant directions around the point of interest, in degrees.
Starting at north and moving clockwise. The series 0, 10, 20, 30, 40,
15, 25, 5 would mean the horizon height is 0° due north, 10° for
north-east, 20° for east, 30° for south-east, and so on. Optional,
Depends on `userhorizon=1`,
- outputformat, str, [csv, basic, json], csv, Output format. csv: CSV with text explanations, basic: CSV. Optional
- browser, bool, 0, 1, 0, Setting browser=1 and accessing the service through a web browser, will save the retrieved data to a file. Optional
"""
pass
time ¶
Important sun and solar surface geometry parameters in calculating the amount of solar radiation that reaches a particular location on the Earth's surface
Functions:
| Name | Description |
|---|---|
correction | |
equation_of_time | |
fractional_year | |
local | |
offset | |
solar_time | Calculate the solar time. |
correction ¶
equation_of_time ¶
fractional_year ¶
local ¶
offset ¶
solar_time ¶
solar_time(
longitude: Annotated[float, typer_argument_longitude],
timestamps: Annotated[
DatetimeIndex, typer_argument_timestamps
] = str(now_utc_datetimezone()),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None,
periods: Annotated[
int | None, typer_option_periods
] = None,
frequency: Annotated[
str | None, typer_option_frequency
] = None,
end_time: Annotated[
datetime | None, typer_option_end_time
] = None,
timezone: Annotated[
str | None, typer_option_timezone
] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT,
solar_time_model: Annotated[
List[SolarTimeModel], typer_option_solar_time_model
] = [milne],
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
csv: Annotated[
Path, typer_option_csv
] = CSV_PATH_DEFAULT,
dtype: Annotated[
str, typer_option_dtype
] = DATA_TYPE_DEFAULT,
array_backend: Annotated[
str, typer_option_array_backend
] = ARRAY_BACKEND_DEFAULT,
verbose: Annotated[
int, typer_option_verbose
] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[
int, typer_option_log
] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[
bool, typer_option_fingerprint
] = FINGERPRINT_FLAG_DEFAULT,
index: Annotated[
bool, typer_option_index
] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[
bool, typer_option_quiet
] = QUIET_FLAG_DEFAULT,
)
Calculate the solar time.
-
Map the day of the year onto the circumference of a circle, essentially converting the day of the year into radians.
-
Approximate empirically the equation of time, which accounts for the elliptical shape of Earth's orbit and the tilt of its axis.
-
Calculate the solar time by adding the current hour of the year, the time offset from the equation of time, and the hour offset (likely a longitude-based correction).
Source code in pvgisprototype/cli/time.py
@app.command(
"solar",
no_args_is_help=True,
help=f"{SYMBOL_HOUR_ANGLE} Calculate the apparent solar time",
)
def solar_time(
longitude: Annotated[float, typer_argument_longitude],
timestamps: Annotated[DatetimeIndex, typer_argument_timestamps] = str(
now_utc_datetimezone()
),
start_time: Annotated[
datetime | None, typer_option_start_time
] = None, # Used by a callback function
periods: Annotated[
int | None, typer_option_periods
] = None, # Used by a callback function
frequency: Annotated[
str | None, typer_option_frequency
] = None, # Used by a callback function
end_time: Annotated[
datetime | None, typer_option_end_time
] = None, # Used by a callback function
timezone: Annotated[str | None, typer_option_timezone] = None,
random_timestamps: Annotated[
bool, typer_option_random_timestamps
] = RANDOM_TIMESTAMPS_FLAG_DEFAULT, # Used by a callback function
solar_time_model: Annotated[List[SolarTimeModel], typer_option_solar_time_model] = [
SolarTimeModel.milne
],
angle_output_units: Annotated[
str, typer_option_angle_output_units
] = ANGLE_OUTPUT_UNITS_DEFAULT,
rounding_places: Annotated[
int, typer_option_rounding_places
] = ROUNDING_PLACES_DEFAULT,
csv: Annotated[Path, typer_option_csv] = CSV_PATH_DEFAULT,
dtype: Annotated[str, typer_option_dtype] = DATA_TYPE_DEFAULT,
array_backend: Annotated[str, typer_option_array_backend] = ARRAY_BACKEND_DEFAULT,
verbose: Annotated[int, typer_option_verbose] = VERBOSE_LEVEL_DEFAULT,
log: Annotated[int, typer_option_log] = LOG_LEVEL_DEFAULT,
fingerprint: Annotated[bool, typer_option_fingerprint] = FINGERPRINT_FLAG_DEFAULT,
index: Annotated[bool, typer_option_index] = INDEX_IN_TABLE_OUTPUT_FLAG_DEFAULT,
quiet: Annotated[bool, typer_option_quiet] = QUIET_FLAG_DEFAULT,
):
"""Calculate the solar time.
1. Map the day of the year onto the circumference of a circle, essentially
converting the day of the year into radians.
2. Approximate empirically the equation of time, which accounts for the
elliptical shape of Earth's orbit and the tilt of its axis.
3. Calculate the solar time by adding the current hour of the year, the
time offset from the equation of time, and the hour offset (likely a
longitude-based correction).
"""
# Note the input timestamp and timezone
user_requested_timestamps = timestamps
user_requested_timezone = timezone # Set to UTC by the callback functon !
timezone = utc_zoneinfo = ZoneInfo("UTC")
logger.debug(
f"Input time zone : {timezone}",
alt=f"Input time zone : [code]{timezone}[/code]",
)
if timestamps.tz is None:
timestamps = timestamps.tz_localize(utc_zoneinfo)
logger.debug(
f"Naive input timestamps\n({user_requested_timestamps})\nlocalized to UTC aware for all internal calculations :\n{timestamps}"
)
elif timestamps.tz != utc_zoneinfo:
timestamps = timestamps.tz_convert(utc_zoneinfo)
logger.debug(
f"Input zone\n{user_requested_timezone}\n& timestamps :\n{user_requested_timestamps}\n\nconverted for all internal calculations to :\n{timestamps}",
alt=f"Input zone : [code]{user_requested_timezone}[/code]\n& timestamps :\n{user_requested_timestamps}\n\nconverted for all internal calculations to :\n{timestamps}",
)
solar_time_models = select_models(
SolarTimeModel, solar_time_model
) # Using a callback fails!
solar_time_series = calculate_solar_time_series(
longitude=longitude,
timestamps=timestamps,
timezone=timezone,
solar_time_models=solar_time_models,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
longitude = convert_float_to_degrees_if_requested(longitude, angle_output_units)
if not quiet:
from pvgisprototype.cli.print.solar_time import print_solar_time_series_table
print_solar_time_series_table(
longitude=longitude,
timestamps=timestamps,
timezone=timezone,
solar_time_series=solar_time_series,
title="Solar Time Overview",
rounding_places=rounding_places,
index=index,
)
if csv:
pass
write ¶
Functions:
| Name | Description |
|---|---|
collect_leaf_columns | Recursively collect leaf keys and their values from nested dict. |
create_csv_export_panel | Create a Rich Panel displaying CSV export information. |
flatten_dict_for_csv | Recursively flatten nested OrderedDicts, producing |
print_csv_export_info | Print a formatted info panel for CSV export. |
safe_get_value | Parameters |
write_irradiance_csv | Write time series data to a CSV file in a structured format along with |
write_metadata | |
write_solar_position_series_csv | Write the "output" of solar position overview data to a CSV file. |
write_spectral_factor_csv | Write the spectral factor data to a CSV file. |
write_surface_position_csv | |
collect_leaf_columns ¶
Recursively collect leaf keys and their values from nested dict. Return dict of leaf_key -> value.
Source code in pvgisprototype/cli/write.py
def collect_leaf_columns(d, out=None):
"""
Recursively collect leaf keys and their values from nested dict.
Return dict of leaf_key -> value.
"""
if out is None:
out = {}
for key, val in d.items():
if isinstance(val, (dict, OrderedDict)):
collect_leaf_columns(val, out)
else:
# leaf key only, no prefix
out[key] = val
return out
create_csv_export_panel ¶
Create a Rich Panel displaying CSV export information.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename | Path | Output CSV file path | required |
num_rows | int | Number of data rows written | required |
num_columns | int | Number of columns in CSV | required |
location_info | str | Location/coordinate information | required |
time_range_info | str | Time range information | required |
Returns:
| Type | Description |
|---|---|
Panel | Rich Panel with formatted export information |
Source code in pvgisprototype/cli/write.py
def create_csv_export_panel(
filename: Path,
num_rows: int,
num_columns: int,
) -> Panel:
"""
Create a Rich Panel displaying CSV export information.
Parameters
----------
filename : Path
Output CSV file path
num_rows : int
Number of data rows written
num_columns : int
Number of columns in CSV
location_info : str, optional
Location/coordinate information
time_range_info : str, optional
Time range information
Returns
-------
Panel
Rich Panel with formatted export information
"""
# Create info table
info_table = Table(box=None, show_header=False, padding=(0, 1))
info_table.add_column(style="cyan", width=20)
info_table.add_column(style="white")
# Add rows with icons and info
info_table.add_row("📁 File", Text(str(filename), style="bold green"))
info_table.add_row("_ Rows", Text(str(num_rows), style="bold yellow"))
info_table.add_row("| Columns", Text(str(num_columns), style="bold blue"))
panel = Panel(
info_table,
title="[bold]CSV output[/bold]",
title_align="left",
border_style="dim",
box=ROUNDED,
padding=(1, 2),
expand=False,
)
return panel
flatten_dict_for_csv ¶
Recursively flatten nested OrderedDicts, producing keys as tuples and values as scalars, arrays, or lists.
Source code in pvgisprototype/cli/write.py
def flatten_dict_for_csv(d, out=None):
"""
Recursively flatten nested OrderedDicts, producing
keys as tuples and values as scalars, arrays, or lists.
"""
if out is None:
out = {}
for k, v in d.items():
if isinstance(v, dict) or isinstance(v, OrderedDict):
flatten_dict_for_csv(v, out)
else:
# Use only the leaf key (column name), not the full path
out[str(k)] = v
return out
print_csv_export_info ¶
Print a formatted info panel for CSV export.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename | Path | Output CSV file path | required |
num_rows | int | Number of data rows written | required |
num_columns | int | Number of columns in CSV | required |
Source code in pvgisprototype/cli/write.py
def print_csv_export_info(
filename: Path,
num_rows: int,
num_columns: int,
) -> None:
"""
Print a formatted info panel for CSV export.
Parameters
----------
filename : Path
Output CSV file path
num_rows : int
Number of data rows written
num_columns : int
Number of columns in CSV
"""
from rich.console import Console
console = Console()
panel = create_csv_export_panel(
filename=filename,
num_rows=num_rows,
num_columns=num_columns,
)
console.print(panel)
safe_get_value ¶
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dictionary | Input dictionary | required | |
key | key to retrieve from the dictionary | required | |
index | index ... ? | required |
Returns:
| Type | Description |
|---|---|
The value corresponding to the given `key` in the `dictionary` or the | |
default value if the key does not exist. | |
Source code in pvgisprototype/cli/write.py
def safe_get_value(dictionary, key, index, default=NOT_AVAILABLE):
"""
Parameters
----------
dictionary: dict
Input dictionary
key: str
key to retrieve from the dictionary
index: int
index ... ?
Returns
-------
The value corresponding to the given `key` in the `dictionary` or the
default value if the key does not exist.
"""
value = dictionary.get(key, default)
# if isinstance(value, ndarray) and value.size > 1:
if isinstance(value, (list, ndarray)) and len(value) > index:
return value[index]
return value
write_irradiance_csv ¶
write_irradiance_csv(
longitude=None,
latitude=None,
timestamps=None,
dictionary=None,
index=False,
filename=Path("irradiance.csv"),
)
Write time series data to a CSV file in a structured format along with non-time-series scalar metadata to a separate [JSON | YAML] file.
This function takes location information (longitude and latitude), a time series (timestamps), and a (nested) dictionary containing irradiance or photovoltaic power data, generated by PVGIS' API functions, and writes them into a CSV file.
Attention ! The function modifies the input dictionary by removing certain keys (such as 'Title' and 'Fingerprint') to avoid repeated values in the output file. It is essential to place this function last in the workflow if the original dictionary is needed elsewhere in the code, as it alters the input dictionary in place to save memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
longitude | float | Longitude of the location to include in the CSV output. | None |
latitude | float | Latitude of the location to include in the CSV output. | None |
timestamps | list | A Pandas DatetimeIndex. Each timestamp will correspond to a row in the CSV file. | None |
dictionary | dict | A dictionary containing the irradiance or photovoltaic data, where each key is a variable name and each value is either a list/array of data or a single value to be replicated for all timestamps. | None |
index | bool | If True, an index column will be added to the CSV file, where each row will be numbered sequentially. | False |
filename | Path | The output file path where the CSV will be saved. Defaults to "irradiance.csv". | Path('irradiance.csv') |
Notes
-
Attention : this function is optimized to avoid deep copying the dictionary, reducing memory consumption. It should be placed at the end of any process that requires the original dictionary to remain unmodified !
-
Fingerprint information is removed from the input dictionary and added as part of the filename.
-
Single float or integer values in the dictionary are expanded to match the length of the timestamps.
-
This function expects the
photovoltaic_power_output_series.componentsstructure to match the format required for writing to CSV.
Example
write_irradiance_csv( longitude=-3.7038, latitude=40.4168, timestamps=some_timestamps, dictionary=some_data, filename=Path("output.csv"), index=True )
This will generate a CSV file named 'output.csv' with the specified data and location.
Source code in pvgisprototype/cli/write.py
def write_irradiance_csv(
longitude=None,
latitude=None,
timestamps=None,
dictionary=None,
index=False,
filename=Path("irradiance.csv"),
):
"""
Write time series data to a CSV file in a structured format along with
non-time-series scalar metadata to a separate [JSON | YAML] file.
This function takes location information (longitude and latitude), a time
series (timestamps), and a (nested) dictionary containing irradiance or
photovoltaic power data, generated by PVGIS' API functions, and writes them
into a CSV file.
Attention ! The function modifies the input dictionary by removing certain
keys (such as 'Title' and 'Fingerprint') to avoid repeated values in the
output file. It is essential to place this function last in the workflow if
the original dictionary is needed elsewhere in the code, as it alters the
input dictionary in place to save memory.
Parameters
----------
longitude : float, optional
Longitude of the location to include in the CSV output.
latitude : float, optional
Latitude of the location to include in the CSV output.
timestamps : list, optional
A Pandas DatetimeIndex. Each timestamp will correspond to a row in the
CSV file.
dictionary : dict
A dictionary containing the irradiance or photovoltaic data, where
each key is a variable name and each value is either a list/array of
data or a single value to be replicated for all timestamps.
index : bool, optional
If True, an index column will be added to the CSV file, where each
row will be numbered sequentially.
filename : Path, optional
The output file path where the CSV will be saved. Defaults to
"irradiance.csv".
Notes
-----
- Attention : this function is optimized to avoid deep copying the
dictionary, reducing memory consumption. It should be placed _at the end_
of any process that requires the original dictionary to remain unmodified
!
- Fingerprint information is removed from the input dictionary and added
as part of the filename.
- Single float or integer values in the dictionary are expanded to match
the length of the timestamps.
- This function expects the `photovoltaic_power_output_series.components`
structure to match the format required for writing to CSV.
Example
-------
>>> write_irradiance_csv(
longitude=-3.7038,
latitude=40.4168,
timestamps=some_timestamps,
dictionary=some_data,
filename=Path("output.csv"),
index=True
)
This will generate a CSV file named 'output.csv' with the specified
data and location.
"""
if dictionary is None or timestamps is None:
raise ValueError("Both dictionary and timestamps must be provided.")
filename = Path(filename)
leaf_columns = collect_leaf_columns(dictionary)
time_series_columns = []
metadata = {}
for column_name, val in leaf_columns.items():
# Identify time series arrays matching the timestamps length
if isinstance(val, (np.ndarray, list)) and len(val) == len(timestamps):
time_series_columns.append((column_name, val))
else:
metadata[column_name] = val
header = []
if index:
header.append("Index")
if longitude is not None:
header.append("Longitude")
if latitude is not None:
header.append("Latitude")
header.append("Time")
header.extend([col for col, _ in time_series_columns])
rows = []
for idx, time in enumerate(timestamps):
row = []
if index:
row.append(idx)
if longitude is not None:
row.append(longitude)
if latitude is not None:
row.append(latitude)
row.append(time.strftime("%Y-%m-%d %H:%M:%S"))
for _, array in time_series_columns:
value = array[idx] if array is not None and len(array) > idx else ""
if isinstance(value, (float, np.floating)):
value = round_float_values(float(value), 4)
row.append(value)
rows.append(row)
# Write to CSV
fingerprint = retrieve_fingerprint(dictionary)
# Add me with a flag ? ------------------------------------------
# if not fingerprint:
# fingerprint = Timestamp.now().isoformat(timespec="seconds")
# # Sanitize the ISO datetime for a safe filename
# ---------------------------------------------------------------
if fingerprint:
safe_fingerprint = re.sub(r"[:]", "-", fingerprint) # Replace colons with hyphens
safe_fingerprint = safe_fingerprint.replace(" ", "T") # Ensure ISO format with 'T'
filename = filename.with_stem(filename.stem + f"_{safe_fingerprint}")
with filename.open("w", newline="") as f:
writer = csv.writer(f)
writer.writerow(header)
writer.writerows(rows)
write_metadata(
metadata=metadata,
filename=filename,
formats=("yaml"),
)
print_csv_export_info(
filename=filename,
num_rows=len(rows),
num_columns=len(header),
)
write_metadata ¶
Source code in pvgisprototype/cli/write.py
def write_metadata(
metadata,
filename,
formats=("json", "yaml", "txt"),
):
"""
"""
metadata_filename = filename.with_name(filename.stem + "_metadata")
if "json" in formats:
import json
with open(Path(f"{metadata_filename}.json"), "w") as f:
json.dump(metadata, f, indent=2, default=str)
if "yaml" in formats:
from pvgisprototype.core.hashing import convert_numpy_to_json_serializable
safe_metadata = convert_numpy_to_json_serializable(metadata)
import yaml
with open(Path(f"{metadata_filename}.yaml"), "w") as f:
yaml.safe_dump(safe_metadata, f, sort_keys=False, allow_unicode=True)
if "txt" in formats:
with open(Path(f"{metadata_filename}.txt"), "w") as f:
for k, v in metadata.items():
f.write(f"{k}: {v}\n")
write_solar_position_series_csv ¶
write_solar_position_series_csv(
longitude: float,
latitude: float,
timestamps,
timezone: str,
table: dict,
position_parameters: Sequence[SolarPositionParameter],
index: bool = False,
rounding_places: int = 2,
filename: Path = Path("solar_position_overview.csv"),
) -> None
Write the "output" of solar position overview data to a CSV file.
This function flattens the nested table dictionary and writes time-series data as CSV, handling numpy arrays, enums, and special types.
Source code in pvgisprototype/cli/write.py
def write_solar_position_series_csv(
longitude: float,
latitude: float,
timestamps,
timezone: str,
table: dict,
position_parameters: Sequence[SolarPositionParameter],
index: bool = False,
rounding_places: int = 2,
filename: Path = Path("solar_position_overview.csv"),
) -> None:
"""
Write the "output" of solar position overview data to a CSV file.
This function flattens the nested table dictionary and writes time-series
data as CSV, handling numpy arrays, enums, and special types.
"""
import csv
import re
from numpy import datetime64, isnat, bool_
from pandas import to_datetime, isna
import numpy
def find_nested_value(d: dict, key: str):
"""
Helper function to find nested values
"""
if key in d:
return d[key]
for v in d.values():
if isinstance(v, dict):
found = find_nested_value(v, key)
if found is not None:
return found
return None
def value_in_dict(value, d):
"""Check if value is in dictionary by object identity."""
for v in d.values():
if v is value:
return True
return False
def get_scalar(value_array, idx, rounding_places):
"""
Helper to safely get a scalar value from an array at a specific index
"""
if value_array is None:
return None
if not hasattr(value_array, "__len__"):
return value_array
if len(value_array) <= idx:
return None
value = value_array[idx]
# Round numeric values
if isinstance(value, (int, float, numpy.floating, numpy.integer)):
return round(float(value), rounding_places)
return value
# Extract the first model result (e.g., 'noaa')
first_model_key = next(iter(table))
model_result = table[first_model_key]
# Extract core data and events data
core_data = model_result.get("Core", {})
events_data = model_result.get("Solar Events", {})
algorithms_data = model_result.get("Solar Position Algorithms", {})
# Build header columns
header = []
columns_to_extract = [] # Store (header_name, data_source_dict) tuples
if index:
header.append("Index")
header.extend(["Longitude", "Latitude", "Time", "Timezone"])
# Add columns for each requested parameter
for parameter in position_parameters:
# Skip enum members without a matching ColumnName
if parameter.name not in SolarPositionParameterColumnName.__members__:
continue
# Get the human-readable column name
column_name = SolarPositionParameterColumnName[parameter.name].value
# Find where this data lives
value = None
source_dict = None
if column_name in core_data:
value = core_data[column_name]
source_dict = core_data
elif column_name in events_data:
value = events_data[column_name]
source_dict = events_data
elif column_name in algorithms_data:
value = algorithms_data[column_name]
source_dict = algorithms_data
else:
# Try nested search
value = find_nested_value(model_result, column_name)
if value is not None:
# Use identity check instead of 'in' to avoid array comparison
if value_in_dict(value, core_data):
source_dict = core_data
elif value_in_dict(value, events_data):
source_dict = events_data
elif value_in_dict(value, algorithms_data):
source_dict = algorithms_data
if value is None:
continue
# For event columns, check if there's actual data
if parameter in (
SolarPositionParameter.event_type,
SolarPositionParameter.event_time,
):
if not hasattr(value, "__iter__") or isinstance(value, str):
value_list = [value]
else:
value_list = value
def is_real_event(ev):
if isinstance(ev, datetime64):
return not isnat(ev)
if ev is not None and hasattr(ev, "name") and ev.name == "none":
return False
return ev not in (None, "None")
has_data = any(is_real_event(v) for v in value_list)
if not has_data:
continue
# Clean column name for CSV (remove special characters)
clean_column_name = re.sub(r"[^A-Za-z0-9 ]+", "", column_name).strip()
header.append(clean_column_name)
columns_to_extract.append((column_name, source_dict))
# Build rows
rows = []
for idx, timestamp in enumerate(timestamps):
row = []
if index:
row.append(str(idx))
# Add location and time info
row.append(str(longitude))
row.append(str(latitude))
row.append(to_datetime(timestamp).strftime("%Y-%m-%d %H:%M:%S"))
row.append(str(timezone))
# Extract each parameter value for this timestamp
for column_name, source_dict in columns_to_extract:
if source_dict is None:
row.append("")
continue
value_array = source_dict.get(column_name)
value = get_scalar(value_array, idx, rounding_places)
# Format value for CSV
if value is None or (isinstance(value, float) and isna(value)):
row.append("")
elif isinstance(value, SolarEvent):
row.append(value.value)
elif isinstance(value, datetime64):
if isnat(value):
row.append("")
else:
dt = value.astype("datetime64[s]").astype("O")
row.append(str(dt.time()))
elif isinstance(value, (bool_, bool)):
row.append(str(bool(value)))
elif isinstance(value, (int, float, numpy.generic)):
row.append(str(value))
else:
row.append(str(value))
rows.append(row)
# Write to CSV
with open(filename, "w", newline="") as file:
writer = csv.writer(file)
writer.writerow(header)
writer.writerows(rows)
print_csv_export_info(
filename=filename,
num_rows=len(rows),
num_columns=len(header),
)
write_spectral_factor_csv ¶
write_spectral_factor_csv(
longitude,
latitude,
timestamps: DatetimeIndex,
spectral_factor_dictionary: Dict,
filename: Path = Path("spectral_factor.csv"),
index: bool = False,
)
Write the spectral factor data to a CSV file.
Source code in pvgisprototype/cli/write.py
def write_spectral_factor_csv(
longitude,
latitude,
timestamps: DatetimeIndex,
spectral_factor_dictionary: Dict,
filename: Path = Path("spectral_factor.csv"),
index: bool = False,
):
"""
Write the spectral factor data to a CSV file.
Parameters
----------
- longitude: Longitude of the location.
- latitude: Latitude of the location.
- timestamps: DatetimeIndex of the time series.
- spectral_factor_dictionary: Dictionary containing spectral factor data.
- filename: Path for the output CSV file.
- index: Whether to include the index in the CSV.
"""
header = []
if index:
header.append("Index")
if longitude:
header.append("Longitude")
if latitude:
header.append("Latitude")
header.append("Time")
# Prepare the data for each spectral factor model and module type
data_rows = []
for spectral_factor_model, result in spectral_factor_dictionary.items():
for module_type, data in result.items():
spectral_factor_series = data.get(SPECTRAL_FACTOR_COLUMN_NAME)
# If spectral_factor_series is a scalar, expand it to match the length of timestamps
if isinstance(spectral_factor_series, (float, int)):
spectral_factor_series = full(len(timestamps), spectral_factor_series)
# Add the header for this particular module type and spectral factor model
header.append(f"{module_type.value} ({spectral_factor_model.name})")
# Prepare the rows
for idx, timestamp in enumerate(timestamps):
if len(data_rows) <= idx:
data_row = []
if index:
data_row.append(idx)
if longitude and latitude:
data_row.extend([longitude, latitude])
data_row.append(timestamp.strftime("%Y-%m-%d %H:%M:%S"))
data_rows.append(data_row)
# Append spectral factor data for this timestamp and module type
data_rows[idx].append(spectral_factor_series[idx])
# Write to CSV
with filename.open("w", newline="") as file:
writer = csv.writer(file)
writer.writerow(header) # Write header
writer.writerows(data_rows) # Write rows of data
write_surface_position_csv ¶
write_surface_position_csv(
longitude: float,
latitude: float,
timestamps: list = [],
timezone: str | None = None,
dictionary: dict = {},
fingerprint: str | None = None,
index: bool = False,
filename: Path = Path("optimal_surface_position.csv"),
) -> None
Source code in pvgisprototype/cli/write.py
def write_surface_position_csv(
longitude: float,
latitude: float,
timestamps: list = [],
timezone: str | None = None,
dictionary: dict = {},
fingerprint: str | None = None,
index: bool = False,
filename: Path = Path("optimal_surface_position.csv"),
) -> None:
"""
"""
# remove 'Title' and 'Fingerprint' : we don't want repeated values ! ----
dictionary.pop("Title", NOT_AVAILABLE)
fingerprint = dictionary.pop(FINGERPRINT_COLUMN_NAME, None)
if not fingerprint:
fingerprint = Timestamp.now().isoformat(timespec="seconds")
# Sanitize the ISO datetime for a safe filename
safe_fingerprint = re.sub(r"[:]", "-", fingerprint) # Replace colons with hyphens
safe_fingerprint = safe_fingerprint.replace(" ", "T") # Ensure ISO format with 'T'
# ------------------------------------------------------------- Important
header: list = []
if index:
header.insert(0, "Index")
if longitude:
header.append("Longitude")
if latitude:
header.append("Latitude")
header.append("Start Time")
header.append("End Time")
header.append("Timezone")
header.extend(dictionary.keys())
# Convert special types to strings
for key, value in dictionary.items():
if isinstance(value, generic): # NumPy scalar
dictionary[key] = str(value)
elif (isinstance(value, SurfaceOrientation) or isinstance(value, SurfaceTilt)) and hasattr(value, "value"): # Enums or custom objects with .value
dictionary[key] = str(value.value)
elif isinstance(value, (float, int)):
dictionary[key] = str(value)
# Compose single row
row = []
if index:
row.append(0)
row.extend([
longitude, # type:ignore[list-item]
latitude, # type:ignore[list-item]
timestamps[0].strftime("%Y-%m-%d %H:%M:%S"),
timestamps[-1].strftime("%Y-%m-%d %H:%M:%S"),
timezone, # type:ignore[list-item]
])
row.extend(dictionary.values())
rows = [row]
# Generate filename with fingerprint
if fingerprint:
filename = filename.with_stem(filename.stem + f"_{safe_fingerprint}")
# Write to CSV
with filename.open("w", newline="") as file:
writer = csv.writer(file)
writer.writerow(header)
writer.writerows(rows)